From ad9ecbe1b4f36f5e0f295e01527dab5e83f1edb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A5le=20Pedersen?= Date: Tue, 28 Apr 2026 14:44:42 +0200 Subject: [PATCH] feat: replace picocli with aesh for CLI parsing Migrate jbang's CLI framework from picocli to aesh, leveraging aesh's compile-time annotation processor to eliminate runtime reflection and improve startup performance. Key changes: - Replace all picocli annotations (@Command, @Option, @Parameters) with aesh equivalents (@CommandDefinition, @GroupCommandDefinition, @Option, @Argument, @Arguments, @OptionList, @OptionGroup, @Mixin) - Convert Callable commands to Command with CommandResult return type - Port custom type converters, parameter consumers, and preprocessors to aesh Converter and OptionParser interfaces - Implement external plugin command discovery in help output - Add default value provider using aesh's post-parse processing - Wire mutual exclusion validation via aesh's exclusiveWith - Restore version checking, dynamic completion, option aliases, negatable options, and grouped help sections - Regenerate native-image configuration for aesh - Update tests for aesh CLI parsing Performance (vs picocli, median): - Native image: 6ms vs 33ms (5.5x faster) for single commands - JDK 25: 109ms vs 228ms (2.1x faster) - JDK 11: 149ms vs 269ms (1.8x faster) Picks up fixes for inherited option propagation (#421) and @OptionGroup keys without values (#422). --- .gitignore | 3 + build.gradle | 27 +- .../java/dev/jbang/it/AbstractHelpBaseIT.java | 2 +- src/main/java/dev/jbang/Main.java | 125 +- src/main/java/dev/jbang/cli/AIOptions.java | 20 - src/main/java/dev/jbang/cli/Alias.java | 563 ++--- src/main/java/dev/jbang/cli/App.java | 847 +++---- .../java/dev/jbang/cli/BaseBuildCommand.java | 61 +- src/main/java/dev/jbang/cli/BaseCommand.java | 144 +- src/main/java/dev/jbang/cli/Build.java | 20 +- src/main/java/dev/jbang/cli/BuildMixin.java | 61 +- src/main/java/dev/jbang/cli/Cache.java | 162 +- src/main/java/dev/jbang/cli/Catalog.java | 549 +++-- .../jbang/cli/CatalogFileOptionsMixin.java | 48 + .../jbang/cli/CommaSeparatedConverter.java | 13 - src/main/java/dev/jbang/cli/Completion.java | 320 +-- src/main/java/dev/jbang/cli/Config.java | 375 ++-- .../java/dev/jbang/cli/DebugOptionParser.java | 64 + .../dev/jbang/cli/DependencyInfoMixin.java | 36 +- .../jbang/cli/DeprecatedMessageHandler.java | 47 - src/main/java/dev/jbang/cli/Deps.java | 213 +- src/main/java/dev/jbang/cli/Edit.java | 94 +- src/main/java/dev/jbang/cli/Export.java | 1404 ++++++------ src/main/java/dev/jbang/cli/ExportMixin.java | 59 - .../jbang/cli/ExternalCommandsProvider.java | 87 + src/main/java/dev/jbang/cli/FormatMixin.java | 14 - src/main/java/dev/jbang/cli/HelpMixin.java | 10 - src/main/java/dev/jbang/cli/Info.java | 608 ++--- src/main/java/dev/jbang/cli/Init.java | 139 +- src/main/java/dev/jbang/cli/JBang.java | 545 +---- .../jbang/cli/JBangDefaultValueProvider.java | 62 + src/main/java/dev/jbang/cli/Jdk.java | 510 +++-- .../java/dev/jbang/cli/JdkProvidersMixin.java | 26 +- .../java/dev/jbang/cli/KeyValueConsumer.java | 32 - src/main/java/dev/jbang/cli/NativeMixin.java | 11 +- src/main/java/dev/jbang/cli/Run.java | 161 +- src/main/java/dev/jbang/cli/RunMixin.java | 83 +- src/main/java/dev/jbang/cli/ScriptMixin.java | 45 +- .../dev/jbang/cli/StrictOptionParser.java | 39 + .../cli/StrictParameterPreprocessor.java | 27 - src/main/java/dev/jbang/cli/Template.java | 707 +++--- .../jbang/cli/TemplatePropertyConverter.java | 42 - src/main/java/dev/jbang/cli/Trust.java | 110 +- src/main/java/dev/jbang/cli/Version.java | 32 +- .../java/dev/jbang/cli/VersionProvider.java | 12 - src/main/java/dev/jbang/cli/Wrapper.java | 124 +- .../java/dev/jbang/util/ConsoleOutput.java | 33 +- .../config/reachability-metadata.json | 1991 ++++------------- src/native-image/config/reflect-config.json | 93 - src/test/java/dev/jbang/BaseTest.java | 30 +- .../java/dev/jbang/TestConfiguration.java | 31 +- .../cli/TemplatePropertyConverterTest.java | 90 +- .../java/dev/jbang/cli/TestAeshParsing.java | 222 ++ src/test/java/dev/jbang/cli/TestAlias.java | 107 +- src/test/java/dev/jbang/cli/TestApp.java | 68 +- .../java/dev/jbang/cli/TestArguments.java | 66 +- src/test/java/dev/jbang/cli/TestCatalog.java | 4 +- src/test/java/dev/jbang/cli/TestConfig.java | 47 +- src/test/java/dev/jbang/cli/TestDeps.java | 44 +- src/test/java/dev/jbang/cli/TestEdit.java | 29 +- src/test/java/dev/jbang/cli/TestExport.java | 69 +- .../java/dev/jbang/cli/TestExternalDeps.java | 22 +- src/test/java/dev/jbang/cli/TestInfo.java | 77 +- src/test/java/dev/jbang/cli/TestInit.java | 84 +- src/test/java/dev/jbang/cli/TestJdk.java | 177 +- src/test/java/dev/jbang/cli/TestPlugins.java | 8 +- src/test/java/dev/jbang/cli/TestRun.java | 394 ++-- .../dev/jbang/cli/TestStartupBenchmark.java | 147 ++ src/test/java/dev/jbang/cli/TestTemplate.java | 67 +- 69 files changed, 5622 insertions(+), 6961 deletions(-) delete mode 100644 src/main/java/dev/jbang/cli/AIOptions.java create mode 100644 src/main/java/dev/jbang/cli/CatalogFileOptionsMixin.java delete mode 100644 src/main/java/dev/jbang/cli/CommaSeparatedConverter.java create mode 100644 src/main/java/dev/jbang/cli/DebugOptionParser.java delete mode 100644 src/main/java/dev/jbang/cli/DeprecatedMessageHandler.java delete mode 100644 src/main/java/dev/jbang/cli/ExportMixin.java create mode 100644 src/main/java/dev/jbang/cli/ExternalCommandsProvider.java delete mode 100644 src/main/java/dev/jbang/cli/FormatMixin.java delete mode 100644 src/main/java/dev/jbang/cli/HelpMixin.java create mode 100644 src/main/java/dev/jbang/cli/JBangDefaultValueProvider.java delete mode 100644 src/main/java/dev/jbang/cli/KeyValueConsumer.java create mode 100644 src/main/java/dev/jbang/cli/StrictOptionParser.java delete mode 100644 src/main/java/dev/jbang/cli/StrictParameterPreprocessor.java delete mode 100644 src/main/java/dev/jbang/cli/TemplatePropertyConverter.java delete mode 100644 src/main/java/dev/jbang/cli/VersionProvider.java delete mode 100644 src/native-image/config/reflect-config.json create mode 100644 src/test/java/dev/jbang/cli/TestAeshParsing.java create mode 100644 src/test/java/dev/jbang/cli/TestStartupBenchmark.java diff --git a/.gitignore b/.gitignore index 003bae80f..c97d5920d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ allure-results .DS_Store .env assets/ +.cachebro/ +CLAUDE.md +PLAN-*.md diff --git a/build.gradle b/build.gradle index 5356c1ca8..18dc02c1a 100644 --- a/build.gradle +++ b/build.gradle @@ -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' @@ -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) { @@ -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 } diff --git a/src/it/java/dev/jbang/it/AbstractHelpBaseIT.java b/src/it/java/dev/jbang/it/AbstractHelpBaseIT.java index 8d94dda29..72fcc1720 100644 --- a/src/it/java/dev/jbang/it/AbstractHelpBaseIT.java +++ b/src/it/java/dev/jbang/it/AbstractHelpBaseIT.java @@ -17,7 +17,7 @@ public abstract class AbstractHelpBaseIT extends BaseIT { public void shouldPrintHelp() { assertThat(shell("jbang " + commandName() + " --help")) .succeeded() - .errContains("Use 'jbang -h' for detailed"); + .outContains("--help"); } // Scenario: check help command is printed when -h is requested diff --git a/src/main/java/dev/jbang/Main.java b/src/main/java/dev/jbang/Main.java index ad67b547d..68423d653 100644 --- a/src/main/java/dev/jbang/Main.java +++ b/src/main/java/dev/jbang/Main.java @@ -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 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 subcommandNames; + + static Set getSubcommandNames() { + if (subcommandNames == null) { + Set 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 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 leadingOpts = new ArrayList<>(); List remainingArgs = new ArrayList<>(); boolean foundParam = false; @@ -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 @@ -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 jbangOpts = stripNonInheritedJBangOpts(leadingOpts); List result = new ArrayList<>(jbangOpts); result.add("run"); @@ -97,6 +182,22 @@ private static boolean hasRunOpts(List 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 stripNonInheritedJBangOpts(List opts) { List jbangOpts = opts.stream() .filter(o -> "--preview".equals(o) || o.startsWith("--preview=")) diff --git a/src/main/java/dev/jbang/cli/AIOptions.java b/src/main/java/dev/jbang/cli/AIOptions.java deleted file mode 100644 index 12b73451f..000000000 --- a/src/main/java/dev/jbang/cli/AIOptions.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.jbang.cli; - -import picocli.CommandLine; - -public class AIOptions { - - @CommandLine.Option(names = { - "--ai-provider" }, description = "Provider to use (openai, openrouter, etc.) when using init with a prompt - if none specified will use the default provider from environment variables", defaultValue = "${JBANG_AI_PROVIDER:-${default.ai.provider)}") - String provider; - @CommandLine.Option(names = { - "--ai-api-key" }, description = "API Key/token to use for the AI Provider", defaultValue = "${JBANG_AI_API_KEY:-${default.ai.api-key)}") - String apiKey; - @CommandLine.Option(names = { - "--ai-endpoint" }, description = "OpenAI compatible Endpoint to use for the AI Provider when using init with a prompt", defaultValue = "${JBANG_AI_ENDPOINT:-${default.ai.endpoint)}") - String endpoint; - @CommandLine.Option(names = { - "--ai-model" }, description = "Model string to ue for init prompting", defaultValue = "${JBANG_AI_MODEL:-${default.ai.model}}") - String model; - -} diff --git a/src/main/java/dev/jbang/cli/Alias.java b/src/main/java/dev/jbang/cli/Alias.java index 84fe5477c..926706e70 100644 --- a/src/main/java/dev/jbang/cli/Alias.java +++ b/src/main/java/dev/jbang/cli/Alias.java @@ -1,17 +1,23 @@ package dev.jbang.cli; +import java.io.IOException; import java.io.PrintStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import dev.jbang.Settings; import dev.jbang.catalog.Alias.JavaAgent; import dev.jbang.catalog.Catalog; import dev.jbang.catalog.CatalogUtil; @@ -21,332 +27,327 @@ import dev.jbang.util.ConsoleOutput; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "alias", description = "Manage aliases for scripts.", subcommands = { AliasAdd.class, - AliasList.class, AliasRemove.class }) -public class Alias { - @CommandLine.Mixin - HelpMixin helpMixin; -} +@GroupCommandDefinition(name = "alias", description = "Manage aliases for scripts.", groupCommands = { + Alias.AliasAdd.class, Alias.AliasList.class, Alias.AliasRemove.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class Alias extends BaseCommand { -abstract class BaseAliasCommand extends BaseCommand { - - @CommandLine.Option(names = { "--global", "-g" }, description = "Use the global (user) catalog file") - boolean global; - - @CommandLine.Option(names = { "--file", "-f" }, description = "Path to the catalog file to use") - Path catalogFile; - - protected Path getCatalog(boolean strict) { - Path cat; - if (global) { - cat = Settings.getUserCatalogFile(); - } else { - if (catalogFile != null && Files.isDirectory(catalogFile)) { - Path defaultCatalog = catalogFile.resolve(Catalog.JBANG_CATALOG_JSON); - Path hiddenCatalog = catalogFile.resolve(Settings.JBANG_DOT_DIR).resolve(Catalog.JBANG_CATALOG_JSON); - if (!Files.exists(defaultCatalog) && Files.exists(hiddenCatalog)) { - cat = hiddenCatalog; - } else { - cat = defaultCatalog; - } - } else { - cat = catalogFile; - } - if (strict && cat != null && !Files.isRegularFile(cat)) { - throw new IllegalArgumentException("Catalog file not found at: " + catalogFile); - } - } - return cat; + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'alias'"); + return EXIT_INVALID_INPUT; } -} -@CommandLine.Command(name = "add", description = "Add alias for script reference.") -class AliasAdd extends BaseAliasCommand { + @CommandDefinition(name = "add", description = "Add alias for script reference.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AliasAdd extends BaseCommand { - @CommandLine.Mixin - ScriptMixin scriptMixin; + @Mixin + ScriptMixin scriptMixin; - @CommandLine.Mixin - BuildMixin buildMixin; + @Mixin + BuildMixin buildMixin; - @CommandLine.Mixin - DependencyInfoMixin dependencyInfoMixin; + @Mixin + DependencyInfoMixin dependencyInfoMixin; - @CommandLine.Mixin - NativeMixin nativeMixin; + @Mixin + NativeMixin nativeMixin; - @CommandLine.Mixin - RunMixin runMixin; + @Mixin + RunMixin runMixin; - @CommandLine.Option(names = { "--description" }, description = "A description for the alias") - String description; + @Mixin + JdkProvidersMixin jdkMixin; - @CommandLine.Option(names = { "--name" }, description = "A name for the alias") - String name; + @Mixin + CatalogFileOptionsMixin catalogOptions; - @CommandLine.Option(names = { - "--force" }, description = "Force overwriting of existing alias") - boolean force; + @Option(name = "description", description = "A description for the alias") + String description; - @CommandLine.Option(names = { "--enable-preview" }, description = "Activate Java preview features") - Boolean enablePreviewRequested; + @Option(name = "name", description = "A name for the alias") + String name; - @CommandLine.Parameters(paramLabel = "params", index = "1..*", arity = "0..*", description = "Parameters to pass on to the script") - List userParams; + @Option(name = "force", hasValue = false, description = "Force overwriting of existing alias") + boolean force; - @CommandLine.Option(names = { "--docs" }, description = "Documentation reference for the alias") - List docs; + @Option(name = "enable-preview", hasValue = false, description = "Activate Java preview features") + Boolean enablePreviewRequested; - @Override - public Integer doCall() { - scriptMixin.validate(); - if (name != null && !Catalog.isValidName(name)) { - throw new IllegalArgumentException( - "Invalid alias name, it should start with a letter followed by 0 or more letters, digits, underscores or hyphens"); - } + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - ProjectBuilder pb = createProjectBuilder(); - Project prj = pb.build(scriptMixin.scriptOrFile); - if (name == null) { - name = CatalogUtil.nameFromRef(scriptMixin.scriptOrFile); - } + @Arguments(description = "Parameters for the script") + List userParams; - String desc = description != null ? description : prj.getDescription().orElse(null); - - dev.jbang.catalog.Alias alias = new dev.jbang.catalog.Alias(scriptMixin.scriptOrFile, desc, userParams, - runMixin.javaRuntimeOptions, scriptMixin.sources, scriptMixin.resources, - dependencyInfoMixin.getDependencies(), - dependencyInfoMixin.getRepositories(), dependencyInfoMixin.getClasspaths(), - dependencyInfoMixin.getProperties(), buildMixin.javaVersion, buildMixin.main, buildMixin.module, - buildMixin.compileOptions, nativeMixin.nativeImage, nativeMixin.nativeOptions, - scriptMixin.forceType != null ? scriptMixin.forceType.name() : null, buildMixin.integrations, - runMixin.flightRecorderString, runMixin.debugString, runMixin.cds, runMixin.interactive, - enablePreviewRequested, runMixin.enableAssertions, runMixin.enableSystemAssertions, - buildMixin.manifestOptions, createJavaAgents(), docs, null); - Path catFile = getCatalog(false); - if (catFile == null) { - catFile = Catalog.getCatalogFile(null); + @Option(name = "docs", description = "Documentation reference for the alias") + List docs; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; + } + dependencyInfoMixin.applyIgnoreTransitiveRepositories(); + runMixin.resolveAfterParse(); } - if (force || !CatalogUtil.hasAlias(catFile, name)) { - CatalogUtil.addAlias(catFile, name, alias); - } else { - Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to add anyway."); - return EXIT_INVALID_INPUT; + + @Override + public Integer doCall() throws IOException { + scriptMixin.validate(); + if (name != null && !Catalog.isValidName(name)) { + throw new ExitException(EXIT_INVALID_INPUT, + "Invalid alias name, it should start with a letter followed by 0 or more letters, digits, underscores or hyphens"); + } + + ProjectBuilder pb = createAliasProjectBuilder(); + Project prj = pb.build(scriptMixin.scriptOrFile); + if (name == null) { + name = CatalogUtil.nameFromRef(scriptMixin.scriptOrFile); + } + + String desc = description != null ? description : prj.getDescription().orElse(null); + + List args = userParams != null && !userParams.isEmpty() ? userParams : null; + dev.jbang.catalog.Alias alias = new dev.jbang.catalog.Alias(scriptMixin.scriptOrFile, desc, args, + runMixin.javaRuntimeOptions, scriptMixin.sources, scriptMixin.resources, + dependencyInfoMixin.getDependencies(), + dependencyInfoMixin.getRepositories(), dependencyInfoMixin.getClasspaths(), + dependencyInfoMixin.getProperties(), buildMixin.javaVersion, buildMixin.main, buildMixin.module, + buildMixin.compileOptions, nativeMixin.nativeImage, nativeMixin.nativeOptions, + scriptMixin.getForceType() != null ? scriptMixin.getForceType().name() : null, + buildMixin.getIntegrations(), + runMixin.flightRecorderString, runMixin.debugString, runMixin.getCds(), runMixin.interactive, + enablePreviewRequested, runMixin.enableAssertions, + runMixin.enableSystemAssertions, + buildMixin.manifestOptions, createJavaAgents(), docs, null); + Path catFile = catalogOptions.getCatalogOrDefault(); + if (force || !CatalogUtil.hasAlias(catFile, name)) { + CatalogUtil.addAlias(catFile, name, alias); + } else { + Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to add anyway."); + return EXIT_INVALID_INPUT; + } + info(String.format("Alias '%s' added to '%s'", name, catFile)); + return EXIT_OK; } - info(String.format("Alias '%s' added to '%s'", name, catFile)); - return EXIT_OK; - } - ProjectBuilder createProjectBuilder() { - ProjectBuilder pb = Project - .builder() - .setProperties(dependencyInfoMixin.getProperties()) - .additionalDependencies(dependencyInfoMixin.getDependencies()) - .additionalRepositories(dependencyInfoMixin.getRepositories()) - .additionalClasspaths(dependencyInfoMixin.getClasspaths()) - .additionalSources(scriptMixin.sources) - .additionalResources(scriptMixin.resources) - .forceType(scriptMixin.forceType) - .javaVersion(buildMixin.javaVersion) - .moduleName(buildMixin.module) - .compileOptions(buildMixin.compileOptions) - .nativeImage(nativeMixin.nativeImage) - .nativeOptions(nativeMixin.nativeOptions) - .integrations(buildMixin.integrations) - .enablePreview(enablePreviewRequested) - .jdkManager(buildMixin.jdkProvidersMixin.getJdkManager()); - Path cat = getCatalog(false); - if (cat != null) { - pb.catalog(cat.toFile()); + ProjectBuilder createAliasProjectBuilder() { + ProjectBuilder pb = Project + .builder() + .setProperties(dependencyInfoMixin.getProperties()) + .additionalDependencies(dependencyInfoMixin.getDependencies()) + .additionalRepositories(dependencyInfoMixin.getRepositories()) + .additionalClasspaths(dependencyInfoMixin.getClasspaths()) + .additionalSources(scriptMixin.sources) + .additionalResources(scriptMixin.resources) + .forceType(scriptMixin.getForceType()) + .javaVersion(buildMixin.javaVersion) + .moduleName(buildMixin.module) + .compileOptions(buildMixin.compileOptions) + .nativeImage(nativeMixin.nativeImage) + .nativeOptions(nativeMixin.nativeOptions) + .integrations(buildMixin.getIntegrations()) + .enablePreview(enablePreviewRequested) + .jdkManager(jdkMixin.getJdkManager()); + Path cat = catalogOptions.getCatalog(false); + if (cat != null) { + pb.catalog(cat.toFile()); + } + return pb; } - return pb; - } - private List createJavaAgents() { - if (runMixin.javaAgentSlots == null) { - return Collections.emptyList(); + private List createJavaAgents() { + if (runMixin.javaAgentSlots == null) { + return Collections.emptyList(); + } + return runMixin.javaAgentSlots.entrySet() + .stream() + .map(e -> new JavaAgent(e.getKey(), e.getValue())) + .collect(Collectors.toList()); } - return runMixin.javaAgentSlots.entrySet() - .stream() - .map(e -> new JavaAgent(e.getKey(), e.getValue())) - .collect(Collectors.toList()); } -} -@CommandLine.Command(name = "list", description = "Lists locally defined aliases or from the given catalog.") -class AliasList extends BaseAliasCommand { + @CommandDefinition(name = "list", description = "Lists locally defined aliases or from the given catalog.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AliasList extends BaseCommand { - @CommandLine.Option(names = { "--show-origin" }, description = "Show the origin of the alias") - boolean showOrigin; + @Mixin + CatalogFileOptionsMixin catalogOptions; - @CommandLine.Parameters(paramLabel = "catalogName", index = "0", description = "The name of a catalog", arity = "0..1") - String catalogName; + @Option(name = "show-origin", hasValue = false, description = "Show the origin of the alias") + boolean showOrigin; - @CommandLine.Mixin - FormatMixin formatMixin; + @Argument(description = "The name of a catalog") + String catalogName; - private static final int INDENT_SIZE = 3; + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; - @Override - public Integer doCall() { - PrintStream out = System.out; - Catalog catalog; - Path cat = getCatalog(true); - if (catalogName != null) { - catalog = Catalog.getByName(catalogName); - } else if (cat != null) { - catalog = Catalog.get(cat); - } else { - catalog = Catalog.getMerged(true, false); - } - if (showOrigin) { - printAliasesWithOrigin(out, catalogName, catalog, formatMixin.format); - } else { - printAliases(out, catalogName, catalog, formatMixin.format); - } - return EXIT_OK; - } + private static final int INDENT_SIZE = 3; - static void printAliases(PrintStream out, String catalogName, Catalog catalog, FormatMixin.Format format) { - List aliases = catalog.aliases - .keySet() - .stream() - .sorted() - .map(name -> getAliasOut(catalogName, catalog, name)) - .collect(Collectors.toList()); - - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(aliases, out); - } else { - aliases.forEach(a -> printAlias(out, a, 0)); + @Override + public Integer doCall() throws IOException { + PrintStream out = System.out; + Catalog catalog; + Path cat = catalogOptions.getCatalog(true); + if (catalogName != null) { + catalog = Catalog.getByName(catalogName); + } else if (cat != null) { + catalog = Catalog.get(cat); + } else { + catalog = Catalog.getMerged(true, false); + } + if (showOrigin) { + printAliasesWithOrigin(out, catalogName, catalog, format); + } else { + printAliases(out, catalogName, catalog, format); + } + return EXIT_OK; } - } - static List getAliasesWithOrigin(String catalogName, Catalog catalog) { - Map> groups = catalog.aliases - .keySet() - .stream() - .sorted() - .map(name -> getAliasOut(catalogName, catalog, - name)) - .collect(Collectors.groupingBy( - a -> a._catalogRef)); - return groups.entrySet() - .stream() - .map(e -> new CatalogList.CatalogOut(null, e.getKey(), - e.getValue(), null, null)) - .collect(Collectors.toList()); - } + static void printAliases(PrintStream out, String catalogName, Catalog catalog, String format) { + List aliases = catalog.aliases + .keySet() + .stream() + .sorted() + .map(name -> getAliasOut(catalogName, catalog, name)) + .collect(Collectors.toList()); + + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(aliases, out); + } else { + aliases.forEach(a -> printAlias(out, a, 0)); + } + } - static void printAliasesWithOrigin(PrintStream out, String catalogName, Catalog catalog, - FormatMixin.Format format) { - List catalogs = getAliasesWithOrigin(catalogName, catalog); - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(catalogs, out); - } else { - catalogs.forEach(cat -> { - out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); - cat.aliases.forEach(a -> printAlias(out, a, 1)); - }); + static List getAliasesWithOrigin(String catalogName, + Catalog catalog) { + Map> groups = catalog.aliases + .keySet() + .stream() + .sorted() + .map(name -> getAliasOut(catalogName, catalog, + name)) + .collect(Collectors.groupingBy( + a -> a._catalogRef)); + return groups.entrySet() + .stream() + .map(e -> new dev.jbang.cli.Catalog.CatalogList.CatalogOut(null, e.getKey(), + e.getValue(), null, null)) + .collect(Collectors.toList()); } - } - static class AliasOut { - public String name; - public String catalogName; - public String fullName; - public String scriptRef; - public String description; - public List arguments; - public String javaVersion; - public String mainClass; - public List javaOptions; - public Map properties; - public transient ResourceRef _catalogRef; - public Boolean enablePreview; - public List docsRef; - } + static void printAliasesWithOrigin(PrintStream out, String catalogName, Catalog catalog, + String format) { + List catalogs = getAliasesWithOrigin(catalogName, catalog); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(catalogs, out); + } else { + catalogs.forEach(cat -> { + out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); + cat.aliases.forEach(a -> printAlias(out, a, 1)); + }); + } + } - private static AliasOut getAliasOut(String catalogName, Catalog catalog, String name) { - dev.jbang.catalog.Alias alias = catalog.aliases.get(name); - String catName = catalogName != null ? Catalog.simplifyRef(catalogName) : CatalogUtil.catalogRef(name); - String fullName = catalogName != null ? name + "@" + catName : name; - String scriptRef = alias.scriptRef; - if (!catalog.aliases.containsKey(scriptRef) - && !Catalog.isValidCatalogReference(scriptRef)) { - scriptRef = alias.resolve(); + static class AliasOut { + public String name; + public String catalogName; + public String fullName; + public String scriptRef; + public String description; + public List arguments; + public String javaVersion; + public String mainClass; + public List javaOptions; + public Map properties; + public transient ResourceRef _catalogRef; + public Boolean enablePreview; + public List docsRef; } - AliasOut out = new AliasOut(); - out.name = name; - out.catalogName = catName; - out.fullName = fullName; - out.scriptRef = scriptRef; - out.description = alias.description; - out.arguments = alias.arguments; - out.javaVersion = alias.javaVersion; - out.mainClass = alias.mainClass; - out.javaOptions = alias.runtimeOptions; - out.properties = alias.properties; - out._catalogRef = alias.catalog.catalogRef; - out.enablePreview = alias.enablePreview; - out.docsRef = alias.docs; - return out; - } + private static AliasOut getAliasOut(String catalogName, Catalog catalog, String name) { + dev.jbang.catalog.Alias alias = catalog.aliases.get(name); + String catName = catalogName != null ? Catalog.simplifyRef(catalogName) : CatalogUtil.catalogRef(name); + String fullName = catalogName != null ? name + "@" + catName : name; + String scriptRef = alias.scriptRef; + if (!catalog.aliases.containsKey(scriptRef) + && !Catalog.isValidCatalogReference(scriptRef)) { + scriptRef = alias.resolve(); + } - private static void printAlias(PrintStream out, AliasOut alias, int indent) { - String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); - String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); - String prefix3 = Util.repeat(" ", (indent + 2) * INDENT_SIZE); - out.println(prefix1 + dev.jbang.cli.CatalogList.getColoredFullName(alias.fullName)); - if (alias.description != null) { - out.println(prefix2 + alias.description); + AliasOut out = new AliasOut(); + out.name = name; + out.catalogName = catName; + out.fullName = fullName; + out.scriptRef = scriptRef; + out.description = alias.description; + out.arguments = alias.arguments; + out.javaVersion = alias.javaVersion; + out.mainClass = alias.mainClass; + out.javaOptions = alias.runtimeOptions; + out.properties = alias.properties; + out._catalogRef = alias.catalog.catalogRef; + out.enablePreview = alias.enablePreview; + out.docsRef = alias.docs; + return out; } - if (alias.docsRef != null && !alias.docsRef.isEmpty()) { - for (String ref : alias.docsRef) { - out.println(prefix2 + ref); + + private static void printAlias(PrintStream out, AliasOut alias, int indent) { + String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); + String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); + String prefix3 = Util.repeat(" ", (indent + 2) * INDENT_SIZE); + out.println(prefix1 + dev.jbang.cli.Catalog.CatalogList.getColoredFullName(alias.fullName)); + if (alias.description != null) { + out.println(prefix2 + alias.description); + } + if (alias.docsRef != null && !alias.docsRef.isEmpty()) { + for (String ref : alias.docsRef) { + out.println(prefix2 + ref); + } + } + out.println(prefix2 + ConsoleOutput.faint(alias.scriptRef)); + if (alias.arguments != null) { + out.println(prefix3 + ConsoleOutput.cyan(" Arguments: ") + String.join(" ", alias.arguments)); + } + if (alias.javaVersion != null) { + out.println(prefix3 + ConsoleOutput.cyan(" Java Version: ") + alias.javaVersion); + } + if (alias.mainClass != null) { + out.println(prefix3 + ConsoleOutput.cyan(" Main Class: ") + alias.mainClass); + } + if (alias.enablePreview != null) { + out.println(prefix3 + ConsoleOutput.cyan(" Enable Preview: ") + alias.enablePreview); + } + if (alias.javaOptions != null) { + out.println(prefix3 + ConsoleOutput.cyan(" Java Options: ") + String.join(" ", alias.javaOptions)); + } + if (alias.properties != null) { + out.println(prefix3 + ConsoleOutput.magenta(" Properties: ") + alias.properties); } - } - out.println(prefix2 + ConsoleOutput.faint(alias.scriptRef)); - if (alias.arguments != null) { - out.println(prefix3 + ConsoleOutput.cyan(" Arguments: ") + String.join(" ", alias.arguments)); - } - if (alias.javaVersion != null) { - out.println(prefix3 + ConsoleOutput.cyan(" Java Version: ") + alias.javaVersion); - } - if (alias.mainClass != null) { - out.println(prefix3 + ConsoleOutput.cyan(" Main Class: ") + alias.mainClass); - } - if (alias.enablePreview != null) { - out.println(prefix3 + ConsoleOutput.cyan(" Enable Preview: ") + alias.enablePreview); - } - if (alias.javaOptions != null) { - out.println(prefix3 + ConsoleOutput.cyan(" Java Options: ") + String.join(" ", alias.javaOptions)); - } - if (alias.properties != null) { - out.println(prefix3 + ConsoleOutput.magenta(" Properties: ") + alias.properties); } } -} -@CommandLine.Command(name = "remove", description = "Remove existing alias.") -class AliasRemove extends BaseAliasCommand { + @CommandDefinition(name = "remove", description = "Remove existing alias.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AliasRemove extends BaseCommand { - @CommandLine.Parameters(paramLabel = "name", index = "0", description = "The name of the alias", arity = "1") - String name; + @Mixin + CatalogFileOptionsMixin catalogOptions; - @Override - public Integer doCall() { - final Path cat = getCatalog(true); - if (cat != null) { - CatalogUtil.removeAlias(cat, name); - } else { - CatalogUtil.removeNearestAlias(name); + @Argument(description = "The name of the alias", required = true) + String name; + + @Override + public Integer doCall() throws IOException { + final Path cat = catalogOptions.getCatalog(true); + if (cat != null) { + CatalogUtil.removeAlias(cat, name); + } else { + CatalogUtil.removeNearestAlias(name); + } + return EXIT_OK; } - return EXIT_OK; } } diff --git a/src/main/java/dev/jbang/cli/App.java b/src/main/java/dev/jbang/cli/App.java index a9d2774b6..13ff93ba5 100644 --- a/src/main/java/dev/jbang/cli/App.java +++ b/src/main/java/dev/jbang/cli/App.java @@ -19,6 +19,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -34,15 +41,15 @@ import dev.jbang.util.UnpackUtil; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "app", description = "Manage scripts installed on the user's PATH as commands.", subcommands = { - AppInstall.class, AppList.class, - AppUninstall.class, AppSetup.class }) -public class App { +@GroupCommandDefinition(name = "app", description = "Manage scripts installed on the user's PATH as commands.", groupCommands = { + App.AppInstall.class, App.AppList.class, App.AppUninstall.class, App.AppSetup.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class App extends BaseCommand { - @CommandLine.Mixin - HelpMixin helpMixin; + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'app'"); + return EXIT_INVALID_INPUT; + } public static void deleteCommandFiles(String name) { try (Stream files = Files.list(Settings.getConfigBinDir())) { @@ -54,503 +61,511 @@ public static void deleteCommandFiles(String name) { } } -} + @CommandDefinition(name = "install", description = "Install a script as a command.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AppInstall extends BaseBuildCommand { + private static final String jbangUrl = "https://www.jbang.dev/releases/latest/download/jbang.zip"; -@CommandLine.Command(name = "install", description = "Install a script as a command.") -class AppInstall extends BaseBuildCommand { - private static final String jbangUrl = "https://www.jbang.dev/releases/latest/download/jbang.zip"; + @Mixin + RunMixin runMixin; - @CommandLine.Option(names = { "--force" }, description = "Force re-installation") - boolean force; + @Option(name = "force", hasValue = false, description = "Force re-installation") + boolean force; - @CommandLine.Option(names = { "--no-build" }, description = "Don't pre-build the code before installation") - boolean noBuild; + @Option(name = "no-build", hasValue = false, description = "Don't pre-build the code before installation") + boolean noBuild; - @CommandLine.Option(names = { "--name" }, description = "A name for the command") - String name; + @Option(name = "name", description = "A name for the command") + String name; - @CommandLine.Mixin - RunMixin runMixin; + @Argument(description = "A file or URL to a Java code file") + public String scriptArg; - @CommandLine.Parameters(index = "1..*", arity = "0..*", description = "Parameters to pass on to the script") - public List userParams = new ArrayList<>(); + @Arguments(description = "Parameters for the script") + public List userParams; - @Override - public Integer doCall() { - scriptMixin.validate(); - // dependencyInfoMixin.validate(); - boolean installed = false; - try { - if ("jbang".equals(scriptMixin.scriptOrFile)) { - if (name != null && !"jbang".equals(name)) { - throw new IllegalArgumentException( - "It's not possible to install jbang with a different name"); - } - installed = installJBang(force); - } else { - if ("jbang".equals(name)) { - throw new IllegalArgumentException("jbang is a reserved name."); - } - if (name != null && !CatalogUtil.isValidName(name)) { - throw new IllegalArgumentException("Not a valid command name: '" + name + "'"); - } - installed = install(); + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; } - if (installed) { - if (AppSetup.needsSetup()) { - return AppSetup.setup(AppSetup.guessWithJava(), false, false); - } + if (userParams == null) { + userParams = new ArrayList<>(); } - } catch (IOException e) { - throw new ExitException(EXIT_INTERNAL_ERROR, "Could not install command", e); + runMixin.resolveAfterParse(); } - return EXIT_OK; - } - private List collectRunOptions() { - List opts = new ArrayList<>(); - opts.addAll(scriptMixin.opts()); - opts.addAll(buildMixin.opts()); - opts.addAll(dependencyInfoMixin.opts()); - opts.addAll(nativeMixin.opts()); - opts.addAll(runMixin.opts()); - if (Boolean.TRUE.equals(enablePreviewRequested)) { - opts.add("--enable-preview"); + @Override + public Integer doCall() throws IOException { + scriptMixin.validate(); + boolean installed = false; + try { + if ("jbang".equals(scriptMixin.scriptOrFile)) { + if (name != null && !"jbang".equals(name)) { + throw new ExitException(EXIT_INVALID_INPUT, + "It's not possible to install jbang with a different name"); + } + installed = installJBang(force); + } else { + if ("jbang".equals(name)) { + throw new ExitException(EXIT_INVALID_INPUT, "jbang is a reserved name."); + } + if (name != null && !CatalogUtil.isValidName(name)) { + throw new ExitException(EXIT_INVALID_INPUT, "Not a valid command name: '" + name + "'"); + } + installed = install(); + } + if (installed) { + if (AppSetup.needsSetup()) { + return AppSetup.setup(AppSetup.guessWithJava(), false, false); + } + } + } catch (IOException e) { + throw new ExitException(EXIT_INTERNAL_ERROR, "Could not install command", e); + } + return EXIT_OK; } - return opts; - } - public boolean install() throws IOException { - Path binDir = Settings.getConfigBinDir(); - if (!force && name != null && existScripts(binDir, name)) { - Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to install anyway."); - return false; + private List collectRunOptions() { + List opts = new ArrayList<>(); + opts.addAll(scriptMixin.opts()); + opts.addAll(buildMixin.opts()); + opts.addAll(dependencyInfoMixin.opts()); + opts.addAll(nativeMixin.opts()); + opts.addAll(runMixin.opts()); + if (Boolean.TRUE.equals(enablePreviewRequested)) { + opts.add("--enable-preview"); + } + return opts; } - String scriptRef = scriptMixin.scriptOrFile; - ProjectBuilder pb = createProjectBuilder(); - Project prj = pb.build(scriptRef); - if (name == null) { - name = CatalogUtil.nameFromRef(scriptRef); - if (!force && existScripts(binDir, name)) { + + public boolean install() throws IOException { + Path binDir = Settings.getConfigBinDir(); + if (!force && name != null && existScripts(binDir, name)) { Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to install anyway."); return false; } + String scriptRef = scriptMixin.scriptOrFile; + ProjectBuilder pb = createBaseProjectBuilder(); + Project prj = pb.build(scriptRef); + if (name == null) { + name = CatalogUtil.nameFromRef(scriptRef); + if (!force && existScripts(binDir, name)) { + Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to install anyway."); + return false; + } + } + if (!ProjectBuilder.isAlias(prj.getResourceRef()) && !DependencyUtil.looksLikeAGav(scriptRef) + && !prj.getResourceRef().isURL()) { + scriptRef = prj.getResourceRef().getFile().toAbsolutePath().toString(); + } + if (!noBuild) { + prj.codeBuilder().build(); + } + installScripts(name, scriptRef, collectRunOptions(), userParams); + Util.infoMsg("Command installed: " + name); + return true; } - if (!ProjectBuilder.isAlias(prj.getResourceRef()) && !DependencyUtil.looksLikeAGav(scriptRef) - && !prj.getResourceRef().isURL()) { - scriptRef = prj.getResourceRef().getFile().toAbsolutePath().toString(); - } - if (!noBuild) { - prj.codeBuilder().build(); - } - installScripts(name, scriptRef, collectRunOptions(), userParams); - Util.infoMsg("Command installed: " + name); - return true; - } - - ProjectBuilder createProjectBuilder() { - return createBaseProjectBuilder().mainClass(buildMixin.main); - } - private static boolean existScripts(Path binDir, String name) { - return Files.exists(binDir.resolve(name)) || Files.exists(binDir.resolve(name + ".cmd")) - || Files.exists(binDir.resolve(name + ".ps1")); - } + private static boolean existScripts(Path binDir, String name) { + return Files.exists(binDir.resolve(name)) || Files.exists(binDir.resolve(name + ".cmd")) + || Files.exists(binDir.resolve(name + ".ps1")); + } - private static void installScripts(String name, String scriptRef, List runOpts, List runArgs) - throws IOException { - Path binDir = Settings.getConfigBinDir(); - binDir.toFile().mkdirs(); - if (Util.isWindows()) { - installCmdScript(binDir.resolve(name + ".cmd"), scriptRef, runOpts, runArgs); - installPSScript(binDir.resolve(name + ".ps1"), scriptRef, runOpts, runArgs); - // Script references on Linux/Mac should never contain backslashes - String nixRef = scriptRef.replace('\\', '/'); - installShellScript(binDir.resolve(name), nixRef, runOpts, runArgs); - } else { - installShellScript(binDir.resolve(name), scriptRef, runOpts, runArgs); + private static void installScripts(String name, String scriptRef, List runOpts, List runArgs) + throws IOException { + Path binDir = Settings.getConfigBinDir(); + binDir.toFile().mkdirs(); + if (Util.isWindows()) { + installCmdScript(binDir.resolve(name + ".cmd"), scriptRef, runOpts, runArgs); + installPSScript(binDir.resolve(name + ".ps1"), scriptRef, runOpts, runArgs); + // Script references on Linux/Mac should never contain backslashes + String nixRef = scriptRef.replace('\\', '/'); + installShellScript(binDir.resolve(name), nixRef, runOpts, runArgs); + } else { + installShellScript(binDir.resolve(name), scriptRef, runOpts, runArgs); + } } - } - private static void installShellScript(Path file, String scriptRef, List runOpts, List runArgs) - throws IOException { - List cmd = new ArrayList<>(); - cmd.addAll(Arrays.asList("exec", "jbang", "run")); - cmd.addAll(runOpts); - cmd.add(scriptRef); - cmd.addAll(runArgs); - CommandBuffer cb = CommandBuffer.of(cmd); - List lines = Arrays.asList("#!/bin/sh", cb.shell(Util.Shell.bash).asCommandLine() + " \"$@\""); - Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - if (!Util.isWindows()) { - Util.setExecutable(file); + private static void installShellScript(Path file, String scriptRef, List runOpts, List runArgs) + throws IOException { + List cmd = new ArrayList<>(); + cmd.addAll(Arrays.asList("exec", "jbang", "run")); + cmd.addAll(runOpts); + cmd.add(scriptRef); + cmd.addAll(runArgs); + CommandBuffer cb = CommandBuffer.of(cmd); + List lines = Arrays.asList("#!/bin/sh", cb.shell(Util.Shell.bash).asCommandLine() + " \"$@\""); + Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + if (!Util.isWindows()) { + Util.setExecutable(file); + } } - } - private static void installCmdScript(Path file, String scriptRef, List runOpts, List runArgs) - throws IOException { - List cmd = new ArrayList<>(); - cmd.addAll(Arrays.asList("jbang", "run")); - cmd.addAll(runOpts); - cmd.add(scriptRef); - cmd.addAll(runArgs); - CommandBuffer cb = CommandBuffer.of(cmd); - List lines = Arrays.asList("@echo off", cb.shell(Util.Shell.cmd).asCommandLine() + " %*"); - Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - } + private static void installCmdScript(Path file, String scriptRef, List runOpts, List runArgs) + throws IOException { + List cmd = new ArrayList<>(); + cmd.addAll(Arrays.asList("jbang", "run")); + cmd.addAll(runOpts); + cmd.add(scriptRef); + cmd.addAll(runArgs); + CommandBuffer cb = CommandBuffer.of(cmd); + List lines = Arrays.asList("@echo off", cb.shell(Util.Shell.cmd).asCommandLine() + " %*"); + Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } - private static void installPSScript(Path file, String scriptRef, List runOpts, List runArgs) - throws IOException { - List cmd = new ArrayList<>(); - cmd.addAll(Arrays.asList("jbang", "run")); - cmd.addAll(runOpts); - cmd.add(scriptRef); - cmd.addAll(runArgs); - CommandBuffer cb = CommandBuffer.of(cmd); - List lines = Collections.singletonList(cb.shell(Util.Shell.powershell).asCommandLine() + " @args"); - Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); - } + private static void installPSScript(Path file, String scriptRef, List runOpts, List runArgs) + throws IOException { + List cmd = new ArrayList<>(); + cmd.addAll(Arrays.asList("jbang", "run")); + cmd.addAll(runOpts); + cmd.add(scriptRef); + cmd.addAll(runArgs); + CommandBuffer cb = CommandBuffer.of(cmd); + List lines = Collections.singletonList(cb.shell(Util.Shell.powershell).asCommandLine() + " @args"); + Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + } - public static boolean installJBang(boolean force) throws IOException { - Path binDir = Settings.getConfigBinDir(); - boolean managedJBang = Files.exists(binDir.resolve("jbang.jar")); + public static boolean installJBang(boolean force) throws IOException { + Path binDir = Settings.getConfigBinDir(); + boolean managedJBang = Files.exists(binDir.resolve("jbang.jar")); - if (!force && (managedJBang || Util.searchPath("jbang") != null)) { - Util.infoMsg("jbang is already available, re-run with --force to install anyway."); - return false; - } + if (!force && (managedJBang || Util.searchPath("jbang") != null)) { + Util.infoMsg("jbang is already available, re-run with --force to install anyway."); + return false; + } - if (force || !managedJBang) { - if (!Util.isOffline()) { - Util.withCacheEvict(() -> { - // Download JBang and unzip to ~/.jbang/bin/ - Util.infoMsg("Downloading and installing jbang..."); - Path zipFile = NetUtil.downloadAndCacheFile(jbangUrl); - Path urlsDir = Settings.getCacheDir(Cache.CacheClass.urls); - Util.deletePath(urlsDir.resolve("jbang"), true); - UnpackUtil.unpack(zipFile, urlsDir); - App.deleteCommandFiles("jbang"); - Path fromDir = urlsDir.resolve("jbang").resolve("bin"); + if (force || !managedJBang) { + if (!Util.isOffline()) { + Util.withCacheEvict(() -> { + // Download JBang and unzip to ~/.jbang/bin/ + Util.infoMsg("Downloading and installing jbang..."); + Path zipFile = NetUtil.downloadAndCacheFile(jbangUrl); + Path urlsDir = Settings.getCacheDir(Cache.CacheClass.urls); + Util.deletePath(urlsDir.resolve("jbang"), true); + UnpackUtil.unpack(zipFile, urlsDir); + App.deleteCommandFiles("jbang"); + Path fromDir = urlsDir.resolve("jbang").resolve("bin"); + copyJBangFiles(fromDir, binDir); + return 0; + }); + } else { + Path jar = Util.getJarLocation(); + // TODO: this is duplicated in Wrapper.java - should be more shared. + if (!jar.toString().endsWith(".jar") && !jar.toString().endsWith("jbang.bin")) { + throw new ExitException(EXIT_GENERIC_ERROR, "Could not determine jbang location from " + jar); + } + Path fromDir = jar.getParent(); + if (fromDir.endsWith(".jbang")) { + fromDir = fromDir.getParent(); + } copyJBangFiles(fromDir, binDir); - return 0; - }); - } else { - Path jar = Util.getJarLocation(); - // TODO: this is duplicated in Wrapper.java - should be more shared. - if (!jar.toString().endsWith(".jar") && !jar.toString().endsWith("jbang.bin")) { - throw new ExitException(EXIT_GENERIC_ERROR, "Could not determine jbang location from " + jar); - } - Path fromDir = jar.getParent(); - if (fromDir.endsWith(".jbang")) { - fromDir = fromDir.getParent(); } - copyJBangFiles(fromDir, binDir); + } else { + Util.infoMsg("jbang is already installed."); } - } else { - Util.infoMsg("jbang is already installed."); + return true; } - return true; - } - private static void copyJBangFiles(Path from, Path to) throws IOException { - to.toFile().mkdirs(); - Stream.of("jbang", "jbang.cmd", "jbang.ps1", "jbang.jar") - .map(Paths::get) - .forEach(f -> { - try { - Path fromp = from.resolve(f); - Path top = to.resolve(f); - if (f.endsWith("jbang.jar")) { - if (!Files.isReadable(fromp)) { - fromp = from.resolve(".jbang/jbang.jar"); - } - if (Util.isWindows() && Files.isRegularFile(top)) { - top = to.resolve("jbang.jar.new"); + private static void copyJBangFiles(Path from, Path to) throws IOException { + to.toFile().mkdirs(); + Stream.of("jbang", "jbang.cmd", "jbang.ps1", "jbang.jar") + .map(Paths::get) + .forEach(f -> { + try { + Path fromp = from.resolve(f); + Path top = to.resolve(f); + if (f.endsWith("jbang.jar")) { + if (!Files.isReadable(fromp)) { + fromp = from.resolve(".jbang/jbang.jar"); + } + if (Util.isWindows() && Files.isRegularFile(top)) { + top = to.resolve("jbang.jar.new"); + } } + Files.copy(fromp, top, StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES); + } catch (IOException e) { + throw new ExitException(EXIT_GENERIC_ERROR, "Could not copy " + f.toString(), e); } - Files.copy(fromp, top, StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.COPY_ATTRIBUTES); - } catch (IOException e) { - throw new ExitException(EXIT_GENERIC_ERROR, "Could not copy " + f.toString(), e); - } - }); + }); + } } -} -@CommandLine.Command(name = "list", description = "Lists installed commands.") -class AppList extends BaseCommand { + @CommandDefinition(name = "list", description = "Lists installed commands.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AppList extends BaseCommand { - @CommandLine.Mixin - FormatMixin formatMixin; + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; - @Override - public Integer doCall() { - if (formatMixin.format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(listCommandFiles(), System.out); - } else { - listCommandFiles().forEach(app -> System.out.println(app.name)); + @Override + public Integer doCall() throws IOException { + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(listCommandFiles(), System.out); + } else { + listCommandFiles().forEach(app -> System.out.println(app.name)); + } + return EXIT_OK; } - return EXIT_OK; - } - static class AppOut { - String name; + static class AppOut { + String name; - public String getName() { - return name; - } + public String getName() { + return name; + } - public AppOut(Path file) { - name = Util.base(file.getFileName().toString()); + public AppOut(Path file) { + name = Util.base(file.getFileName().toString()); + } } - } - private static List listCommandFiles() { - try (Stream files = Files.list(Settings.getConfigBinDir())) { - return files - .filter(Files::isExecutable) - .sorted() - .map(AppOut::new) - .filter(distinctByKey(AppOut::getName)) - .collect(Collectors.toList()); - } catch (IOException e) { - return Collections.emptyList(); + private static List listCommandFiles() { + try (Stream files = Files.list(Settings.getConfigBinDir())) { + return files + .filter(Files::isExecutable) + .sorted() + .map(AppOut::new) + .filter(distinctByKey(AppOut::getName)) + .collect(Collectors.toList()); + } catch (IOException e) { + return Collections.emptyList(); + } } - } - private static Predicate distinctByKey(Function keyExtractor) { - Set seen = ConcurrentHashMap.newKeySet(); - return t -> seen.add(keyExtractor.apply(t)); + private static Predicate distinctByKey(Function keyExtractor) { + Set seen = ConcurrentHashMap.newKeySet(); + return t -> seen.add(keyExtractor.apply(t)); + } } -} -@CommandLine.Command(name = "uninstall", description = "Removes a previously installed command.") -class AppUninstall extends BaseCommand { + @CommandDefinition(name = "uninstall", description = "Removes a previously installed command.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AppUninstall extends BaseCommand { - @CommandLine.Parameters(paramLabel = "name", index = "0", description = "The name of the command", arity = "1") - String name; + @Argument(description = "The name of the command", required = true) + String name; - @Override - public Integer doCall() { - if (commandFilesExist(name)) { - App.deleteCommandFiles(name); - Util.infoMsg("Command removed: " + name); - return EXIT_OK; - } else { - Util.infoMsg("Command not found: " + name); - return EXIT_INVALID_INPUT; + @Override + public Integer doCall() throws IOException { + if (commandFilesExist(name)) { + App.deleteCommandFiles(name); + Util.infoMsg("Command removed: " + name); + return EXIT_OK; + } else { + Util.infoMsg("Command not found: " + name); + return EXIT_INVALID_INPUT; + } } - } - private static boolean commandFilesExist(String name) { - try (Stream files = Files.list(Settings.getConfigBinDir())) { - return files.anyMatch(f -> f.getFileName().toString().equals(name) - || f.getFileName().toString().startsWith(name + ".")); - } catch (IOException e) { - return false; + private static boolean commandFilesExist(String name) { + try (Stream files = Files.list(Settings.getConfigBinDir())) { + return files.anyMatch(f -> f.getFileName().toString().equals(name) + || f.getFileName().toString().startsWith(name + ".")); + } catch (IOException e) { + return false; + } } } -} -@CommandLine.Command(name = "setup", description = "Make jbang commands available for the user.") -class AppSetup extends BaseCommand { + @CommandDefinition(name = "setup", description = "Make jbang commands available for the user.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class AppSetup extends BaseCommand { - @CommandLine.Option(names = { - "--java" }, description = "Add JBang's Java to the user's environment as well", negatable = true) - Boolean java; + @Option(name = "java", negatable = true, description = "Add JBang's Java to the user's environment as well") + Boolean java; - @CommandLine.Option(names = { - "--force" }, description = "Force setup to be performed even when existing configuration has been detected") - boolean force; + @Option(name = "force", hasValue = false, description = "Force setup to be performed even when existing configuration has been detected") + boolean force; - @Override - public Integer doCall() { - boolean withJava; - if (java == null) { - withJava = guessWithJava(); - } else { - withJava = java; + @Override + public Integer doCall() throws IOException { + boolean withJava; + if (java == null) { + withJava = guessWithJava(); + } else { + withJava = java; + } + return setup(withJava, force, true); } - return setup(withJava, force, true); - } - - public static boolean needsSetup() { - String envPath = System.getenv("PATH"); - Path binDir = Settings.getConfigBinDir(); - return !envPath.toLowerCase().contains(binDir.toString().toLowerCase()); - } - - /** - * Makes a best guess if JAVA_HOME should be set by us or not. Returns true if - * no JAVA_HOME is set and javac wasn't found on the PATH and we have at least - * one managed JDK installed by us. Otherwise it returns false. - */ - public static boolean guessWithJava() { - boolean withJava; - Jdk defJdk = defaultJdkManager().getJdk(null); - String javaHome = System.getenv("JAVA_HOME"); - Path javacCmd = Util.searchPath("javac"); - withJava = defJdk != null - && (javaHome == null - || javaHome.isEmpty() - || javaHome.toLowerCase().startsWith(Settings.getConfigDir().toString().toLowerCase())) - && (javacCmd == null || javacCmd.startsWith(Settings.getConfigBinDir())); - return withJava; - } - public static int setup(boolean withJava, boolean force, boolean chatty) { - Path jdkHome = null; - if (withJava) { - Jdk defJdk = defaultJdkManager().getDefaultJdk(); - if (defJdk == null) { - Util.infoMsg("No default JDK set, use 'jbang jdk default ' to set one."); - return EXIT_UNEXPECTED_STATE; - } - jdkHome = Settings.getDefaultJdkDir(); + public static boolean needsSetup() { + String envPath = System.getenv("PATH"); + Path binDir = Settings.getConfigBinDir(); + return !envPath.toLowerCase().contains(binDir.toString().toLowerCase()); } - Path binDir = Settings.getConfigBinDir(); - binDir.toFile().mkdirs(); + /** + * Makes a best guess if JAVA_HOME should be set by us or not. Returns true if + * no JAVA_HOME is set and javac wasn't found on the PATH and we have at least + * one managed JDK installed by us. Otherwise it returns false. + */ + public static boolean guessWithJava() { + boolean withJava; + Jdk defJdk = defaultJdkManager().getJdk(null); + String javaHome = System.getenv("JAVA_HOME"); + Path javacCmd = Util.searchPath("javac"); + withJava = defJdk != null + && (javaHome == null + || javaHome.isEmpty() + || javaHome.toLowerCase().startsWith(Settings.getConfigDir().toString().toLowerCase())) + && (javacCmd == null || javacCmd.startsWith(Settings.getConfigBinDir())); + return withJava; + } - boolean changed = false; - String cmd = ""; - // Permanently add JBang's bin folder to the user's PATH - if (Util.isWindows()) { - String env = ""; + public static int setup(boolean withJava, boolean force, boolean chatty) { + Path jdkHome = null; if (withJava) { - String newPath = jdkHome.resolve("bin") + ";"; - env += " ; [Environment]::SetEnvironmentVariable('Path', '" + newPath + "' + " + - "[Environment]::GetEnvironmentVariable('Path', [EnvironmentVariableTarget]::User), " + - "[EnvironmentVariableTarget]::User)"; - env += " ; [Environment]::SetEnvironmentVariable('JAVA_HOME', '" + jdkHome + "', " + - "[EnvironmentVariableTarget]::User)"; - } - if (force || needsSetup()) { - // Create the command to change the user's PATH - String newPath = binDir + ";"; - env += " ; [Environment]::SetEnvironmentVariable('Path', '" + newPath + "' + " + - "[Environment]::GetEnvironmentVariable('Path', [EnvironmentVariableTarget]::User), " + - "[EnvironmentVariableTarget]::User)"; - } - if (!env.isEmpty()) { - if (Util.getShell() == Util.Shell.powershell) { - cmd = "{ " + env + " }"; - } else { - cmd = "powershell -NoProfile -ExecutionPolicy Bypass -NonInteractive -Command \"" + env + "\" & "; + Jdk defJdk = defaultJdkManager().getDefaultJdk(); + if (defJdk == null) { + Util.infoMsg("No default JDK set, use 'jbang jdk default ' to set one."); + return EXIT_UNEXPECTED_STATE; } - changed = true; + jdkHome = Settings.getDefaultJdkDir(); } - } else { - if (force || needsSetup() || withJava) { - // Update shell startup scripts - if (Util.isMac()) { - Path bashFile = getHome().resolve(".bash_profile"); - changed = changeBashOrZshRcScript(binDir, jdkHome, bashFile) || changed; + + Path binDir = Settings.getConfigBinDir(); + binDir.toFile().mkdirs(); + + boolean changed = false; + String cmd = ""; + // Permanently add JBang's bin folder to the user's PATH + if (Util.isWindows()) { + String env = ""; + if (withJava) { + String newPath = jdkHome.resolve("bin") + ";"; + env += " ; [Environment]::SetEnvironmentVariable('Path', '" + newPath + "' + " + + "[Environment]::GetEnvironmentVariable('Path', [EnvironmentVariableTarget]::User), " + + "[EnvironmentVariableTarget]::User)"; + env += " ; [Environment]::SetEnvironmentVariable('JAVA_HOME', '" + jdkHome + "', " + + "[EnvironmentVariableTarget]::User)"; } - if (!changed) { - Path bashFile = getHome().resolve(".bashrc"); - changed = changeBashOrZshRcScript(binDir, jdkHome, bashFile) || changed; + if (force || needsSetup()) { + // Create the command to change the user's PATH + String newPath = binDir + ";"; + env += " ; [Environment]::SetEnvironmentVariable('Path', '" + newPath + "' + " + + "[Environment]::GetEnvironmentVariable('Path', [EnvironmentVariableTarget]::User), " + + "[EnvironmentVariableTarget]::User)"; } - Path zshRcFile = getHome().resolve(".zshrc"); - changed = changeBashOrZshRcScript(binDir, jdkHome, zshRcFile) || changed; + if (!env.isEmpty()) { + if (Util.getShell() == Util.Shell.powershell) { + cmd = "{ " + env + " }"; + } else { + cmd = "powershell -NoProfile -ExecutionPolicy Bypass -NonInteractive -Command \"" + env + + "\" & "; + } + changed = true; + } + } else { + if (force || needsSetup() || withJava) { + // Update shell startup scripts + if (Util.isMac()) { + Path bashFile = getHome().resolve(".bash_profile"); + changed = changeBashOrZshRcScript(binDir, jdkHome, bashFile) || changed; + } + if (!changed) { + Path bashFile = getHome().resolve(".bashrc"); + changed = changeBashOrZshRcScript(binDir, jdkHome, bashFile) || changed; + } + Path zshRcFile = getHome().resolve(".zshrc"); + changed = changeBashOrZshRcScript(binDir, jdkHome, zshRcFile) || changed; - Path fishRcFile = getHome().resolve(".config/fish/conf.d/jbang.fish"); - changed = changeFishRc(binDir, jdkHome, fishRcFile) || changed; + Path fishRcFile = getHome().resolve(".config/fish/conf.d/jbang.fish"); + changed = changeFishRc(binDir, jdkHome, fishRcFile) || changed; + } } - } - if (changed) { - Util.infoMsg("JBang environment setup completed..."); - } else if (chatty) { - Util.infoMsg("JBang is already available in PATH."); - Util.infoMsg("(You can use --force to perform the setup anyway)"); - } - if (Util.getShell() == Util.Shell.bash) { if (changed) { - System.err.println("Please start a new Shell for changes to take effect"); + Util.infoMsg("JBang environment setup completed..."); + } else if (chatty) { + Util.infoMsg("JBang is already available in PATH."); + Util.infoMsg("(You can use --force to perform the setup anyway)"); } - } else { - if (changed) { - if (Util.getShell() == Util.Shell.powershell) { - System.err.println("Please start a new PowerShell for changes to take effect"); - } else { - System.err.println("Please open a new CMD window for changes to take effect"); + if (Util.getShell() == Util.Shell.bash) { + if (changed) { + System.err.println("Please start a new Shell for changes to take effect"); + } + } else { + if (changed) { + if (Util.getShell() == Util.Shell.powershell) { + System.err.println("Please start a new PowerShell for changes to take effect"); + } else { + System.err.println("Please open a new CMD window for changes to take effect"); + } } } + if (!cmd.isEmpty()) { + System.out.println(cmd); + return EXIT_EXECUTE; + } else { + return EXIT_OK; + } } - if (!cmd.isEmpty()) { - System.out.println(cmd); - return EXIT_EXECUTE; - } else { - return EXIT_OK; - } - } - private static boolean changeFishRc(Path binDir, Path jdkHome, Path fishRcFile) { - boolean jbangFound = Files.exists(fishRcFile); - if (jbangFound) { - Util.verboseMsg("JBang setup lines already present in " + fishRcFile); - return false; - } + private static boolean changeFishRc(Path binDir, Path jdkHome, Path fishRcFile) { + boolean jbangFound = Files.exists(fishRcFile); + if (jbangFound) { + Util.verboseMsg("JBang setup lines already present in " + fishRcFile); + return false; + } - try { - List lines = new ArrayList(); - lines.add("# Add JBang to environment\n"); - lines.add("abbr --add j! jbang\n"); - lines.add("fish_add_path " + binDir + "\n"); - Files.write(fishRcFile, lines, StandardOpenOption.CREATE_NEW); - Util.verboseMsg("Added JBang setup lines " + fishRcFile); - } catch (IOException e) { - Util.verboseMsg("Couldn't change script: " + fishRcFile, e); - return false; + try { + List lines = new ArrayList(); + lines.add("# Add JBang to environment\n"); + lines.add("abbr --add j! jbang\n"); + lines.add("fish_add_path " + binDir + "\n"); + Files.write(fishRcFile, lines, StandardOpenOption.CREATE_NEW); + Util.verboseMsg("Added JBang setup lines " + fishRcFile); + } catch (IOException e) { + Util.verboseMsg("Couldn't change script: " + fishRcFile, e); + return false; + } + return true; } - return true; - } - private static boolean changeBashOrZshRcScript(Path binDir, Path javaHome, Path rcFile) { - try { - // Detect if JBang has already been set up before - boolean jbangFound = Files.exists(rcFile) - && Files.lines(rcFile) - .anyMatch(ln -> ln.trim().startsWith("#") && ln.toLowerCase().contains("jbang")); - if (!jbangFound) { - // Add lines to add JBang to PATH - String lines = "\n# Add JBang to environment\n" + - "alias j!=jbang\n"; - if (javaHome != null) { - lines += "export PATH=\"" + toHomePath(binDir) + ":" + toHomePath(javaHome.resolve("bin")) - + ":$PATH\"\n" + - "export JAVA_HOME=" + toHomePath(javaHome) + "\n"; + private static boolean changeBashOrZshRcScript(Path binDir, Path javaHome, Path rcFile) { + try { + // Detect if JBang has already been set up before + boolean jbangFound = Files.exists(rcFile) + && Files.lines(rcFile) + .anyMatch(ln -> ln.trim().startsWith("#") && ln.toLowerCase().contains("jbang")); + if (!jbangFound) { + // Add lines to add JBang to PATH + String lines = "\n# Add JBang to environment\n" + + "alias j!=jbang\n"; + if (javaHome != null) { + lines += "export PATH=\"" + toHomePath(binDir) + ":" + toHomePath(javaHome.resolve("bin")) + + ":$PATH\"\n" + + "export JAVA_HOME=" + toHomePath(javaHome) + "\n"; + } else { + lines += "export PATH=\"" + toHomePath(binDir) + ":$PATH\"\n"; + } + Files.write(rcFile, lines.getBytes(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); + Util.verboseMsg("Added JBang setup lines " + rcFile); + return true; } else { - lines += "export PATH=\"" + toHomePath(binDir) + ":$PATH\"\n"; + Util.verboseMsg("JBang setup lines already present in " + rcFile); } - Files.write(rcFile, lines.getBytes(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); - Util.verboseMsg("Added JBang setup lines " + rcFile); - return true; - } else { - Util.verboseMsg("JBang setup lines already present in " + rcFile); + } catch (IOException e) { + Util.verboseMsg("Couldn't change script: " + rcFile, e); } - } catch (IOException e) { - Util.verboseMsg("Couldn't change script: " + rcFile, e); + return false; } - return false; - } - private static Path getHome() { - return Paths.get(System.getProperty("user.home")); - } + private static Path getHome() { + return Paths.get(System.getProperty("user.home")); + } - private static String toHomePath(Path path) { - Path home = getHome(); - if (path.startsWith(home)) { - if (Util.isWindows()) { - return "%userprofile%\\" + home.relativize(path); + private static String toHomePath(Path path) { + Path home = getHome(); + if (path.startsWith(home)) { + if (Util.isWindows()) { + return "%userprofile%\\" + home.relativize(path); + } else { + return "$HOME/" + home.relativize(path); + } } else { - return "$HOME/" + home.relativize(path); + return path.toString(); } - } else { - return path.toString(); } } } diff --git a/src/main/java/dev/jbang/cli/BaseBuildCommand.java b/src/main/java/dev/jbang/cli/BaseBuildCommand.java index 7ebe7011a..fd965e095 100644 --- a/src/main/java/dev/jbang/cli/BaseBuildCommand.java +++ b/src/main/java/dev/jbang/cli/BaseBuildCommand.java @@ -1,34 +1,63 @@ package dev.jbang.cli; import java.nio.file.Path; +import java.nio.file.Paths; -import dev.jbang.source.*; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; -import picocli.CommandLine; +import dev.jbang.devkitman.Jdk; +import dev.jbang.devkitman.JdkManager; +import dev.jbang.source.Project; +import dev.jbang.source.ProjectBuilder; public abstract class BaseBuildCommand extends BaseCommand { - @CommandLine.Mixin + @Mixin ScriptMixin scriptMixin; - @CommandLine.Mixin + @Mixin BuildMixin buildMixin; - @CommandLine.Mixin + @Mixin DependencyInfoMixin dependencyInfoMixin; - @CommandLine.Mixin + @Mixin NativeMixin nativeMixin; - @CommandLine.Option(names = { - "--build-dir" }, description = "Use given directory for build results") - Path buildDir; + @Mixin + JdkProvidersMixin jdkMixin; - @CommandLine.Option(names = { "--enable-preview" }, description = "Activate Java preview features") + @Option(name = "build-dir", description = "Use given directory for build results") + String buildDirStr; + + @Option(name = "enable-preview", hasValue = false, description = "Activate Java preview features") Boolean enablePreviewRequested; + @Override + public void afterParse() { + super.afterParse(); + dependencyInfoMixin.applyIgnoreTransitiveRepositories(); + } + + protected JdkManager getJdkManager() { + return jdkMixin.getJdkManager(); + } + + public Jdk getProjectJdk(Project project) { + Jdk jdk = project.projectJdk(); + if (buildMixin.javaVersion != null) { + jdk = getJdkManager().getOrInstallJdk(buildMixin.javaVersion); + } + return jdk; + } + + protected Path getBuildDir() { + return buildDirStr != null ? Paths.get(buildDirStr) : null; + } + protected ProjectBuilder createBaseProjectBuilder() { - return Project + return dev.jbang.source.Project .builder() .setProperties(dependencyInfoMixin.getProperties()) .additionalDependencies(dependencyInfoMixin.getDependencies()) @@ -36,18 +65,18 @@ protected ProjectBuilder createBaseProjectBuilder() { .additionalClasspaths(dependencyInfoMixin.getClasspaths()) .additionalSources(scriptMixin.sources) .additionalResources(scriptMixin.resources) - .forceType(scriptMixin.forceType) - .catalog(scriptMixin.catalog) + .forceType(scriptMixin.getForceType()) + .catalog(scriptMixin.catalog != null ? new java.io.File(scriptMixin.catalog) : null) .javaVersion(buildMixin.javaVersion) .moduleName(buildMixin.module) .compileOptions(buildMixin.compileOptions) .manifestOptions(buildMixin.manifestOptions) .nativeImage(nativeMixin.nativeImage) .nativeOptions(nativeMixin.nativeOptions) - .integrations(buildMixin.integrations) + .integrations(buildMixin.getIntegrations()) .enablePreview(enablePreviewRequested) - .jdkManager(buildMixin.jdkProvidersMixin.getJdkManager()); + .jdkManager(getJdkManager()); - // NB: Do not put `.mainClass(buildMixin.main)` here + // NB: Do not put .mainClass(main) here } } diff --git a/src/main/java/dev/jbang/cli/BaseCommand.java b/src/main/java/dev/jbang/cli/BaseCommand.java index 83d49a978..b8eabdd44 100644 --- a/src/main/java/dev/jbang/cli/BaseCommand.java +++ b/src/main/java/dev/jbang/cli/BaseCommand.java @@ -4,24 +4,27 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; -import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.*; +import org.aesh.command.Command; +import org.aesh.command.CommandLifecycle; +import org.aesh.command.CommandResult; +import org.aesh.command.invocation.CommandInvocation; +import org.aesh.command.option.Option; + import dev.jbang.Configuration; import dev.jbang.util.Util; -import picocli.CommandLine; - -public abstract class BaseCommand implements Callable { +public abstract class BaseCommand implements Command, CommandLifecycle { public static final int EXIT_OK = 0; public static final int EXIT_GENERIC_ERROR = 1; @@ -35,74 +38,54 @@ public abstract class BaseCommand implements Callable { logger.setLevel(Level.SEVERE); } - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; + @Option(name = "config", description = "Path to config file to be used instead of the default") + String configPath; - @CommandLine.Mixin - HelpMixin helpMixin; + @Option(name = "insecure", hasValue = false, description = "Enable insecure trust of all SSL certificates.") + boolean insecure; - @CommandLine.Option(names = { "--config" }, description = "Path to config file to be used instead of the default") - void setConfig(Path config) { - if (Files.isReadable(config)) { - Configuration.instance(Configuration.get(config)); - } else { - warn("Configuration file does not exist or could not be read: " + config); - } - } + @Option(name = "verbose", hasValue = false, inherited = true, exclusiveWith = { "quiet" }, description = "jbang will be verbose on what it does.") + boolean verbose; - @CommandLine.Option(names = { "--insecure" }, description = "Enable insecure trust of all SSL certificates.") - void setInsecure(boolean insecure) { - if (insecure) { - enableInsecure(); - } - } + @Option(name = "quiet", hasValue = false, inherited = true, exclusiveWith = { "verbose" }, description = "jbang will be quiet, only print when error occurs.") + boolean quiet; + + @Option(shortName = 'o', name = "offline", hasValue = false, inherited = true, exclusiveWith = { "fresh" }, description = "Work offline. Fail-fast if dependencies are missing.") + boolean offline; + + @Option(name = "fresh", hasValue = false, inherited = true, exclusiveWith = { "offline" }, description = "Make sure we use fresh (i.e. non-cached) resources.") + boolean fresh; + + @Option(name = "preview", hasValue = false, inherited = true, description = "Enable jbang preview features") + boolean preview; + + @Option(shortName = 'x', name = "stacktrace", hasValue = false, inherited = true, description = "Print exceptions stacktraces to stderr (even when quiet).") + boolean printExceptions; public PrintStream realOut = new PrintStream(new FileOutputStream(FileDescriptor.out)); + protected CommandInvocation commandInvocation; + void debug(String msg) { if (isVerbose()) { - if (spec != null) { - PrintWriter err = spec.commandLine().getErr(); - err.print(Util.getMsgHeader()); - err.println(msg); - } else { - Util.verboseMsg(msg); - } + Util.verboseMsg(msg); } } void info(String msg) { if (!isQuiet()) { - if (spec != null) { - PrintWriter err = spec.commandLine().getErr(); - err.print(Util.getMsgHeader()); - err.println(msg); - } else { - Util.infoMsg(msg); - } + Util.infoMsg(msg); } } void warn(String msg) { if (!isQuiet()) { - if (spec != null) { - PrintWriter err = spec.commandLine().getErr(); - err.print(Util.getMsgHeader()); - err.println(msg); - } else { - Util.warnMsg(msg); - } + Util.warnMsg(msg); } } void error(String msg, Throwable th) { - if (spec != null) { - PrintWriter err = spec.commandLine().getErr(); - err.print(Util.getMsgHeader()); - err.println(msg); - } else { - Util.errorMsg(msg, th); - } + Util.errorMsg(msg, th); } boolean isVerbose() { @@ -145,8 +128,63 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) { } @Override - public Integer call() throws IOException { - return doCall(); + public CommandResult execute(CommandInvocation commandInvocation) throws InterruptedException { + this.commandInvocation = commandInvocation; + + try { + int exitCode = doCall(); + return CommandResult.valueOf(exitCode); + } catch (ExitException e) { + if (e.getStatus() != 0 && e.getMessage() != null) { + Util.errorMsg(null, e); + } + return CommandResult.valueOf(e.getStatus()); + } catch (IOException e) { + throw new ExitException(EXIT_GENERIC_ERROR, e.getMessage(), e); + } + } + + @Override + public void beforeParse() { + Util.setVerbose(false); + Util.setQuiet(false); + Util.setOffline(false); + Util.setFresh(false); + Util.setPreview(false); + } + + @Override + public void afterParse() { + if (configPath != null) { + Path config = Paths.get(configPath); + if (Files.isReadable(config)) { + Configuration.instance(Configuration.get(config)); + } else { + warn("Configuration file does not exist or could not be read: " + config); + } + } + + if (insecure) { + enableInsecure(); + } + if (verbose) { + Util.setVerbose(true); + } + if (quiet) { + Util.setQuiet(true); + } + if (offline) { + Util.setOffline(true); + } + if (fresh) { + Util.setFresh(true); + } + if (preview) { + Util.setPreview(true); + } + if (printExceptions) { + Util.setPrintExceptions(true); + } } public abstract Integer doCall() throws IOException; diff --git a/src/main/java/dev/jbang/cli/Build.java b/src/main/java/dev/jbang/cli/Build.java index 844e2879f..5e762ca59 100644 --- a/src/main/java/dev/jbang/cli/Build.java +++ b/src/main/java/dev/jbang/cli/Build.java @@ -2,22 +2,34 @@ import java.io.IOException; +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Argument; + import dev.jbang.source.BuildContext; import dev.jbang.source.Project; import dev.jbang.source.ProjectBuilder; -import picocli.CommandLine.Command; - -@Command(name = "build", description = "Compiles and stores script in the cache.") +@CommandDefinition(name = "build", description = "Compiles and stores script in the cache.", generateHelp = true, helpGroup = "Essentials", defaultValueProvider = JBangDefaultValueProvider.class) public class Build extends BaseBuildCommand { + @Argument(description = "A file or URL to a Java code file") + String scriptArg; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; + } + } + @Override public Integer doCall() throws IOException { scriptMixin.validate(); ProjectBuilder pb = createProjectBuilderForBuild(); Project prj = pb.build(scriptMixin.scriptOrFile); - Project.codeBuilder(BuildContext.forProject(prj, buildDir)).build(); + Project.codeBuilder(BuildContext.forProject(prj, getBuildDir())).build(); return EXIT_OK; } diff --git a/src/main/java/dev/jbang/cli/BuildMixin.java b/src/main/java/dev/jbang/cli/BuildMixin.java index ed3d0ec15..767bca573 100644 --- a/src/main/java/dev/jbang/cli/BuildMixin.java +++ b/src/main/java/dev/jbang/cli/BuildMixin.java @@ -4,60 +4,32 @@ import java.util.List; import java.util.Map; -import dev.jbang.devkitman.Jdk; -import dev.jbang.source.Project; - -import picocli.CommandLine; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Option; -import picocli.CommandLine.ParameterException; -import picocli.CommandLine.Spec; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionGroup; +import org.aesh.command.option.OptionList; public class BuildMixin { - @Spec - CommandSpec spec; // injected by picocli - + @Option(shortName = 'j', name = "java", description = "JDK version to use for running the script.") public String javaVersion; - @CommandLine.Mixin - JdkProvidersMixin jdkProvidersMixin; - - @CommandLine.Option(names = { "-j", - "--java" }, description = "JDK version to use for running the script.") - void setJavaVersion(String javaVersion) { - if (!javaVersion.matches("\\d+[+]?")) { - throw new ParameterException(spec.commandLine(), - String.format("Invalid version '%s', should be a number optionally followed by a plus sign", - javaVersion)); - } - this.javaVersion = javaVersion; - } - - @CommandLine.Option(names = { "-m", - "--main" }, description = "Main class to use when running. Used primarily for running jar's. Can be a glob pattern using ? and *.") - String main; + @Option(shortName = 'm', name = "main", description = "Main class to use when running. Used primarily for running jar's. Can be a glob pattern using ? and *.") + public String main; - @CommandLine.Option(names = { - "--module" }, arity = "0..1", fallbackValue = "", description = "Treat resource as a module. Optionally with the given module name", preprocessor = StrictParameterPreprocessor.class) - String module; + @Option(name = "module", parser = StrictOptionParser.class, description = "Treat resource as a module. Optionally with the given module name") + public String module; - @CommandLine.Option(names = { "-C", "--compile-option" }, description = "Options to pass to the compiler") + @OptionList(shortName = 'C', name = "compile-option", description = "Options to pass to the compiler") public List compileOptions; - @CommandLine.Option(names = { "--manifest" }, parameterConsumer = KeyValueConsumer.class) + @OptionGroup(name = "manifest") public Map manifestOptions; - @Option(names = { - "--integrations" }, description = "Enable integration execution (default: true)", negatable = true) - public Boolean integrations; + @Option(name = "integrations", hasValue = false, negatable = true, description = "Enable or disable integration execution (default: true)") + Boolean integrations; - public Jdk getProjectJdk(Project project) { - Jdk jdk = project.projectJdk(); - if (javaVersion != null) { - jdk = jdkProvidersMixin.getJdkManager().getOrInstallJdk(javaVersion); - } - return jdk; + public Boolean getIntegrations() { + return integrations; } public List opts() { @@ -74,9 +46,9 @@ public List opts() { opts.add("--module"); opts.add(module); } - if (Boolean.TRUE.equals(integrations)) { + if (Boolean.TRUE.equals(getIntegrations())) { opts.add("--integrations"); - } else if (Boolean.FALSE.equals(integrations)) { + } else if (Boolean.FALSE.equals(getIntegrations())) { opts.add("--no-integrations"); } if (compileOptions != null) { @@ -91,7 +63,6 @@ public List opts() { opts.add(e.getKey() + "=" + e.getValue()); } } - opts.addAll(jdkProvidersMixin.opts()); return opts; } } diff --git a/src/main/java/dev/jbang/cli/Cache.java b/src/main/java/dev/jbang/cli/Cache.java index 574f36f17..8dc5d672d 100644 --- a/src/main/java/dev/jbang/cli/Cache.java +++ b/src/main/java/dev/jbang/cli/Cache.java @@ -6,79 +6,101 @@ import java.util.EnumSet; import java.util.Optional; -import picocli.CommandLine; - -@CommandLine.Command(name = "cache", description = "Manage compiled scripts in the local cache.") -public class Cache { - - @CommandLine.Mixin - HelpMixin helpMixin; - - @CommandLine.Command(name = "clear", description = "Clear the various caches used by jbang. By default this will clear the JAR, script, stdin and URL caches. To clear other caches list them explicitly i.e. '--project' for temporary projects.") - public Integer clear( - @CommandLine.Option(names = { - "--url" }, description = "clear URL cache only", negatable = true) Boolean urls, - @CommandLine.Option(names = { - "--jar" }, description = "clear JAR cache only", negatable = true) Boolean jars, - @CommandLine.Option(names = { - "--deps" }, description = "clear dependency cache only", negatable = true) Boolean deps, - @CommandLine.Option(names = { - "--jdk" }, description = "clear JDK cache only", negatable = true) Boolean jdks, - @CommandLine.Option(names = { - "--kotlinc" }, description = "clear kotlinc cache only", negatable = true) Boolean kotlincs, - @CommandLine.Option(names = { - "--groovyc" }, description = "clear groovyc cache only", negatable = true) Boolean groovys, - @CommandLine.Option(names = { - "--project" }, description = "clear temporary projects cache only", negatable = true) Boolean projects, - @CommandLine.Option(names = { - "--script" }, description = "clear script cache only", negatable = true) Boolean scripts, - @CommandLine.Option(names = { - "--stdin" }, description = "clear stdin cache only", negatable = true) Boolean stdins, - @CommandLine.Option(names = { "--all" }, description = "clear all caches") boolean all) { - EnumSet classes = EnumSet.noneOf(dev.jbang.Cache.CacheClass.class); - - // if all we add everything - if (all) { - classes.addAll(Arrays.asList(dev.jbang.Cache.CacheClass.values())); - } else if (urls == null - && jars == null - && jdks == null - && kotlincs == null - && groovys == null - && projects == null - && scripts == null - && stdins == null - && deps == null) { - // add the default (safe) set - classes.add(dev.jbang.Cache.CacheClass.urls); - classes.add(dev.jbang.Cache.CacheClass.jars); - classes.add(dev.jbang.Cache.CacheClass.scripts); - classes.add(dev.jbang.Cache.CacheClass.stdins); - classes.add(dev.jbang.Cache.CacheClass.deps); - } +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Option; + +@GroupCommandDefinition(name = "cache", description = "Manage compiled scripts in the local cache.", groupCommands = { + Cache.CacheClear.class }, generateHelp = true, helpGroup = "Caching", defaultValueProvider = JBangDefaultValueProvider.class) +public class Cache extends BaseCommand { - // we only toggle on or off those that are actually present - toggleCache(urls, dev.jbang.Cache.CacheClass.urls, classes); - toggleCache(jars, dev.jbang.Cache.CacheClass.jars, classes); - toggleCache(jdks, dev.jbang.Cache.CacheClass.jdks, classes); - toggleCache(kotlincs, dev.jbang.Cache.CacheClass.kotlincs, classes); - toggleCache(kotlincs, dev.jbang.Cache.CacheClass.groovycs, classes); - toggleCache(deps, dev.jbang.Cache.CacheClass.deps, classes); - toggleCache(projects, dev.jbang.Cache.CacheClass.projects, classes); - toggleCache(scripts, dev.jbang.Cache.CacheClass.scripts, classes); - toggleCache(stdins, dev.jbang.Cache.CacheClass.stdins, classes); - - dev.jbang.Cache.CacheClass[] ccs = classes.toArray(new dev.jbang.Cache.CacheClass[0]); - dev.jbang.Cache.clearCache(ccs); - return EXIT_OK; + @Override + public Integer doCall() { + System.err.println("Missing required subcommand for 'cache'"); + return EXIT_INVALID_INPUT; } - private void toggleCache(Boolean b, dev.jbang.Cache.CacheClass cache, EnumSet classes) { - if (Optional.ofNullable(b).isPresent()) { - if (b) { - classes.add(cache); - } else { - classes.remove(cache); + @CommandDefinition(name = "clear", description = "Clear the various caches used by jbang. By default this will clear the JAR, script, stdin and URL caches.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class CacheClear extends BaseCommand { + + @Option(name = "url", hasValue = false, negatable = true, description = "clear URL cache only") + Boolean urls; + + @Option(name = "jar", hasValue = false, negatable = true, description = "clear JAR cache only") + Boolean jars; + + @Option(name = "deps", hasValue = false, negatable = true, description = "clear dependency cache only") + Boolean deps; + + @Option(name = "jdk", hasValue = false, negatable = true, description = "clear JDK cache only") + Boolean jdks; + + @Option(name = "kotlinc", hasValue = false, negatable = true, description = "clear kotlinc cache only") + Boolean kotlincs; + + @Option(name = "groovyc", hasValue = false, negatable = true, description = "clear groovyc cache only") + Boolean groovys; + + @Option(name = "project", hasValue = false, negatable = true, description = "clear temporary projects cache only") + Boolean projects; + + @Option(name = "script", hasValue = false, negatable = true, description = "clear script cache only") + Boolean scripts; + + @Option(name = "stdin", hasValue = false, negatable = true, description = "clear stdin cache only") + Boolean stdins; + + @Option(name = "all", hasValue = false, description = "clear all caches") + boolean all; + + @Override + public Integer doCall() { + EnumSet classes = EnumSet.noneOf(dev.jbang.Cache.CacheClass.class); + + // if all we add everything + if (all) { + classes.addAll(Arrays.asList(dev.jbang.Cache.CacheClass.values())); + } else if (urls == null + && jars == null + && jdks == null + && kotlincs == null + && groovys == null + && projects == null + && scripts == null + && stdins == null + && deps == null) { + // add the default (safe) set + classes.add(dev.jbang.Cache.CacheClass.urls); + classes.add(dev.jbang.Cache.CacheClass.jars); + classes.add(dev.jbang.Cache.CacheClass.scripts); + classes.add(dev.jbang.Cache.CacheClass.stdins); + classes.add(dev.jbang.Cache.CacheClass.deps); + } + + // we only toggle on or off those that are actually present + toggleCache(urls, dev.jbang.Cache.CacheClass.urls, classes); + toggleCache(jars, dev.jbang.Cache.CacheClass.jars, classes); + toggleCache(jdks, dev.jbang.Cache.CacheClass.jdks, classes); + toggleCache(kotlincs, dev.jbang.Cache.CacheClass.kotlincs, classes); + toggleCache(kotlincs, dev.jbang.Cache.CacheClass.groovycs, classes); + toggleCache(deps, dev.jbang.Cache.CacheClass.deps, classes); + toggleCache(projects, dev.jbang.Cache.CacheClass.projects, classes); + toggleCache(scripts, dev.jbang.Cache.CacheClass.scripts, classes); + toggleCache(stdins, dev.jbang.Cache.CacheClass.stdins, classes); + + dev.jbang.Cache.CacheClass[] ccs = classes.toArray(new dev.jbang.Cache.CacheClass[0]); + dev.jbang.Cache.clearCache(ccs); + return EXIT_OK; + } + + private void toggleCache(Boolean b, dev.jbang.Cache.CacheClass cache, + EnumSet classes) { + if (Optional.ofNullable(b).isPresent()) { + if (b) { + classes.add(cache); + } else { + classes.remove(cache); + } } } } diff --git a/src/main/java/dev/jbang/cli/Catalog.java b/src/main/java/dev/jbang/cli/Catalog.java index bf9a42388..53f48a31b 100644 --- a/src/main/java/dev/jbang/cli/Catalog.java +++ b/src/main/java/dev/jbang/cli/Catalog.java @@ -1,357 +1,332 @@ package dev.jbang.cli; +import java.io.IOException; import java.io.PrintStream; -import java.io.PrintWriter; -import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import dev.jbang.Settings; import dev.jbang.catalog.CatalogRef; import dev.jbang.catalog.CatalogUtil; import dev.jbang.resources.ResourceRef; import dev.jbang.util.ConsoleOutput; import dev.jbang.util.Util; -import picocli.CommandLine; +@GroupCommandDefinition(name = "catalog", description = "Manage Catalogs of aliases.", groupCommands = { + Catalog.CatalogAdd.class, Catalog.CatalogUpdate.class, + Catalog.CatalogList.class, Catalog.CatalogRemove.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class Catalog extends BaseCommand { -@CommandLine.Command(name = "catalog", description = "Manage Catalogs of aliases.", subcommands = { CatalogAdd.class, - CatalogUpdate.class, CatalogList.class, CatalogRemove.class }) -public class Catalog { - @CommandLine.Mixin - HelpMixin helpMixin; -} + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'catalog'"); + return EXIT_INVALID_INPUT; + } -abstract class BaseCatalogCommand extends BaseCommand { - - @CommandLine.Option(names = { "--global", "-g" }, description = "Use the global (user) catalog file") - boolean global; - - @CommandLine.Option(names = { "--file", "-f" }, description = "Path to the catalog file to use") - Path catalogFile; - - protected Path getCatalog(boolean strict) { - Path cat; - if (global) { - cat = Settings.getUserCatalogFile(); - } else { - if (catalogFile != null && Files.isDirectory(catalogFile)) { - Path defaultCatalog = catalogFile.resolve(dev.jbang.catalog.Catalog.JBANG_CATALOG_JSON); - Path hiddenCatalog = catalogFile.resolve(Settings.JBANG_DOT_DIR) - .resolve(dev.jbang.catalog.Catalog.JBANG_CATALOG_JSON); - if (!Files.exists(defaultCatalog) && Files.exists(hiddenCatalog)) { - cat = hiddenCatalog; - } else { - cat = defaultCatalog; - } - } else { - cat = catalogFile; - } - if (strict && cat != null && !Files.isRegularFile(cat)) { - throw new IllegalArgumentException("Catalog file not found at: " + catalogFile); - } - } - return cat; + static abstract class BaseCatalogCommand extends BaseCommand { + + @Mixin + CatalogFileOptionsMixin catalogOptions; } -} -@CommandLine.Command(name = "add", description = "Add a catalog.") -class CatalogAdd extends BaseCatalogCommand { + @CommandDefinition(name = "add", description = "Add a catalog.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class CatalogAdd extends BaseCatalogCommand { - @CommandLine.Option(names = { "--description", - "-d" }, description = "A description for the catalog") - String description; + @Option(shortName = 'd', name = "description", description = "A description for the catalog") + String description; - @CommandLine.Option(names = { "--name" }, description = "A name for the catalog") - String name; + @Option(name = "name", description = "A name for the catalog") + String name; - @CommandLine.Option(names = { - "--force" }, description = "Force overwriting of existing catalog") - boolean force; + @Option(name = "force", hasValue = false, description = "Force overwriting of existing catalog") + boolean force; - @CommandLine.Option(names = { - "--import" }, description = "Import catalog items into the catalog's scope") - Boolean importItems; + @Option(name = "import", description = "Import catalog items into the catalog's scope") + Boolean importItems; - @CommandLine.Parameters(paramLabel = "urlOrFile", index = "0", description = "A file or URL to a catalog file", arity = "1") - String urlOrFile; + @Argument(description = "A file or URL to a catalog file", required = true) + String urlOrFile; - @Override - public Integer doCall() { - if (name != null && !dev.jbang.catalog.Catalog.isValidName(name)) { - throw new IllegalArgumentException( - "Invalid catalog name, it should start with a letter followed by 0 or more letters, digits, underscores, hyphens or dots"); - } - if (name == null) { - name = CatalogUtil.nameFromRef(urlOrFile); - } - CatalogRef ref = CatalogRef.get(urlOrFile); - Path catFile = getCatalog(false); - if (catFile == null) { - catFile = dev.jbang.catalog.Catalog.getCatalogFile(null); - } - if (force || !CatalogUtil.hasCatalogRef(catFile, name)) { - CatalogUtil.addCatalogRef(catFile, name, ref.catalogRef, ref.description, importItems); - } else { - Util.infoMsg("A catalog with name '" + name + "' already exists, use '--force' to add anyway."); - return EXIT_INVALID_INPUT; + @Override + public Integer doCall() throws IOException { + if (name != null && !dev.jbang.catalog.Catalog.isValidName(name)) { + throw new ExitException(EXIT_INVALID_INPUT, + "Invalid catalog name, it should start with a letter followed by 0 or more letters, digits, underscores, hyphens or dots"); + } + if (name == null) { + name = CatalogUtil.nameFromRef(urlOrFile); + } + CatalogRef ref = CatalogRef.get(urlOrFile); + Path catFile = catalogOptions.getCatalogOrDefault(); + if (force || !CatalogUtil.hasCatalogRef(catFile, name)) { + CatalogUtil.addCatalogRef(catFile, name, ref.catalogRef, ref.description, importItems); + } else { + Util.infoMsg("A catalog with name '" + name + "' already exists, use '--force' to add anyway."); + return EXIT_INVALID_INPUT; + } + info(String.format("Catalog '%s' added to '%s'", name, catFile)); + return EXIT_OK; } - info(String.format("Catalog '%s' added to '%s'", name, catFile)); - return EXIT_OK; } -} - -@CommandLine.Command(name = "update", description = "Retrieve the latest contents of the catalogs.") -class CatalogUpdate extends BaseCatalogCommand { - @Override - public Integer doCall() { - PrintWriter err = spec.commandLine().getErr(); - Map cats = dev.jbang.catalog.Catalog.getMerged(false, true).catalogs; - cats - .entrySet() - .stream() - .forEach(e -> { - String ref = e.getValue().catalogRef; - err.println("Updating catalog '" + e.getKey() + "' from " + ref + "..."); - Util.freshly(() -> { - try { - dev.jbang.catalog.Catalog.getByRef(ref); - } catch (Exception ex) { - Util.warnMsg("Unable to read catalog " + ref + " (referenced from " - + e.getValue().catalog.catalogRef + ")"); - } - return null; + @CommandDefinition(name = "update", description = "Retrieve the latest contents of the catalogs.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class CatalogUpdate extends BaseCatalogCommand { + + @Override + public Integer doCall() throws IOException { + Map cats = dev.jbang.catalog.Catalog.getMerged(false, true).catalogs; + cats + .entrySet() + .stream() + .forEach(e -> { + String ref = e.getValue().catalogRef; + System.err.println("Updating catalog '" + e.getKey() + "' from " + ref + "..."); + Util.freshly(() -> { + try { + dev.jbang.catalog.Catalog.getByRef(ref); + } catch (Exception ex) { + Util.warnMsg("Unable to read catalog " + ref + " (referenced from " + + e.getValue().catalog.catalogRef + ")"); + } + return null; + }); }); - }); - return EXIT_OK; + return EXIT_OK; + } } -} -@CommandLine.Command(name = "list", description = "Show currently defined catalogs.") -class CatalogList extends BaseCatalogCommand { + @CommandDefinition(name = "list", description = "Show currently defined catalogs.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class CatalogList extends BaseCatalogCommand { - @CommandLine.Option(names = { "--show-origin" }, description = "Show the origin of the catalog") - boolean showOrigin; + @Option(name = "show-origin", hasValue = false, description = "Show the origin of the catalog") + boolean showOrigin; - @CommandLine.Parameters(paramLabel = "name", index = "0", description = "The name of a catalog", arity = "0..1") - String name; + @Argument(description = "The name of a catalog") + String name; - @CommandLine.Mixin - FormatMixin formatMixin; + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; - private static final int INDENT_SIZE = 3; + private static final int INDENT_SIZE = 3; - @Override - public Integer doCall() { - PrintStream out = System.out; - if (name == null) { - dev.jbang.catalog.Catalog catalog; - Path cat = getCatalog(true); - if (cat != null) { - catalog = dev.jbang.catalog.Catalog.get(cat); - } else { - catalog = dev.jbang.catalog.Catalog.getMerged(true, false); - } - if (showOrigin) { - printCatalogsWithOrigin(out, name, catalog, formatMixin.format); - } else { - printCatalogs(out, name, catalog, formatMixin.format); - } - } else { - dev.jbang.catalog.Catalog catalog = dev.jbang.catalog.Catalog.getByName(name); - if (formatMixin.format == FormatMixin.Format.json) { - List aliasCats = AliasList.getAliasesWithOrigin(name, catalog); - List tplCats = TemplateList.getTemplatesWithOrigin(name, catalog, false, false); - List catCats = getCatalogsWithOrigin(name, catalog); - Map cats = new HashMap<>(); - for (CatalogOut cat : aliasCats) { - cats.put(cat.resourceRef, cat); + @Override + public Integer doCall() throws IOException { + PrintStream out = System.out; + if (name == null) { + dev.jbang.catalog.Catalog catalog; + Path cat = catalogOptions.getCatalog(true); + if (cat != null) { + catalog = dev.jbang.catalog.Catalog.get(cat); + } else { + catalog = dev.jbang.catalog.Catalog.getMerged(true, false); } - for (CatalogOut cat : tplCats) { - if (cats.containsKey(cat.resourceRef)) { - cats.get(cat.resourceRef).templates = cat.templates; - } else { - cats.put(cat.resourceRef, cat); - } + if (showOrigin) { + printCatalogsWithOrigin(out, name, catalog, format); + } else { + printCatalogs(out, name, catalog, format); } - for (CatalogOut cat : catCats) { - if (cats.containsKey(cat.resourceRef)) { - cats.get(cat.resourceRef).catalogs = cat.catalogs; - } else { + } else { + dev.jbang.catalog.Catalog catalog = dev.jbang.catalog.Catalog.getByName(name); + if ("json".equals(format)) { + List aliasCats = Alias.AliasList.getAliasesWithOrigin(name, catalog); + List tplCats = Template.TemplateList.getTemplatesWithOrigin(name, catalog, false, + false); + List catCats = getCatalogsWithOrigin(name, catalog); + Map cats = new HashMap<>(); + for (CatalogOut cat : aliasCats) { cats.put(cat.resourceRef, cat); } + for (CatalogOut cat : tplCats) { + if (cats.containsKey(cat.resourceRef)) { + cats.get(cat.resourceRef).templates = cat.templates; + } else { + cats.put(cat.resourceRef, cat); + } + } + for (CatalogOut cat : catCats) { + if (cats.containsKey(cat.resourceRef)) { + cats.get(cat.resourceRef).catalogs = cat.catalogs; + } else { + cats.put(cat.resourceRef, cat); + } + } + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(cats.values(), out); + } else { + if (!catalog.aliases.isEmpty()) { + out.println("Aliases:"); + out.println("--------"); + Alias.AliasList.printAliases(out, name, catalog, "text"); + } + if (!catalog.templates.isEmpty()) { + out.println("Templates:"); + out.println("----------"); + Template.TemplateList.printTemplates(out, name, catalog, false, false, "text"); + } + if (!catalog.catalogs.isEmpty()) { + out.println("Catalogs:"); + out.println("---------"); + printCatalogs(out, name, catalog, "text"); + } } + } + return EXIT_OK; + } + + static void printCatalogs(PrintStream out, String catalogName, dev.jbang.catalog.Catalog catalog, + String format) { + List catalogs = catalog.catalogs + .keySet() + .stream() + .sorted() + .map(name -> getCatalogRefOut(catalogName, catalog, name)) + .collect(Collectors.toList()); + + if ("json".equals(format)) { Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(cats.values(), out); + parser.toJson(catalogs, out); } else { - if (!catalog.aliases.isEmpty()) { - out.println("Aliases:"); - out.println("--------"); - AliasList.printAliases(out, name, catalog, FormatMixin.Format.text); - } - if (!catalog.templates.isEmpty()) { - out.println("Templates:"); - out.println("----------"); - TemplateList.printTemplates(out, name, catalog, false, false, FormatMixin.Format.text); - } - if (!catalog.catalogs.isEmpty()) { - out.println("Catalogs:"); - out.println("---------"); - printCatalogs(out, name, catalog, FormatMixin.Format.text); - } + catalogs.forEach(c -> printCatalogRef(out, c, 0)); } } - return EXIT_OK; - } - static void printCatalogs(PrintStream out, String catalogName, dev.jbang.catalog.Catalog catalog, - FormatMixin.Format format) { - List catalogs = catalog.catalogs - .keySet() - .stream() - .sorted() - .map(name -> getCatalogRefOut(catalogName, catalog, name)) - .collect(Collectors.toList()); - - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(catalogs, out); - } else { - catalogs.forEach(c -> printCatalogRef(out, c, 0)); + static List getCatalogsWithOrigin(String catalogName, dev.jbang.catalog.Catalog catalog) { + Map> groups = catalog.catalogs + .keySet() + .stream() + .sorted() + .map(name -> getCatalogRefOut(catalogName, + catalog, name)) + .collect(Collectors.groupingBy( + c -> c._catalogRef)); + return groups.entrySet() + .stream() + .map(e -> new CatalogOut(null, e.getKey(), + null, null, e.getValue())) + .collect(Collectors.toList()); } - } - - static List getCatalogsWithOrigin(String catalogName, dev.jbang.catalog.Catalog catalog) { - Map> groups = catalog.catalogs - .keySet() - .stream() - .sorted() - .map(name -> getCatalogRefOut(catalogName, - catalog, name)) - .collect(Collectors.groupingBy( - c -> c._catalogRef)); - return groups.entrySet() - .stream() - .map(e -> new CatalogOut(null, e.getKey(), - null, null, e.getValue())) - .collect(Collectors.toList()); - } - static void printCatalogsWithOrigin(PrintStream out, String catalogName, dev.jbang.catalog.Catalog catalog, - FormatMixin.Format format) { - List catalogs = getCatalogsWithOrigin(catalogName, catalog); - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(catalogs, out); - } else { - catalogs.forEach(cat -> { - out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); - cat.catalogs.forEach(c -> printCatalogRef(out, c, 1)); - }); + static void printCatalogsWithOrigin(PrintStream out, String catalogName, dev.jbang.catalog.Catalog catalog, + String format) { + List catalogs = getCatalogsWithOrigin(catalogName, catalog); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(catalogs, out); + } else { + catalogs.forEach(cat -> { + out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); + cat.catalogs.forEach(c -> printCatalogRef(out, c, 1)); + }); + } } - } - static class CatalogOut { - public String name; - public String resourceRef; - public String description; - public List aliases; - public List templates; - public List catalogs; - - public CatalogOut(String name, ResourceRef ref, List aliases, - List templates, List catalogs) { - this.name = name; - if (name == null) { - if (aliases != null) { - this.name = aliases.get(0).catalogName; - } - if (templates != null) { - this.name = templates.get(0).catalogName; - } - if (catalogs != null) { - this.name = catalogs.get(0).catalogName; + static class CatalogOut { + public String name; + public String resourceRef; + public String description; + public List aliases; + public List templates; + public List catalogs; + + public CatalogOut(String name, ResourceRef ref, List aliases, + List templates, List catalogs) { + this.name = name; + if (name == null) { + if (aliases != null) { + this.name = aliases.get(0).catalogName; + } + if (templates != null) { + this.name = templates.get(0).catalogName; + } + if (catalogs != null) { + this.name = catalogs.get(0).catalogName; + } } + resourceRef = ref.getOriginalResource(); + this.aliases = aliases; + this.templates = templates; + this.catalogs = catalogs; } - resourceRef = ref.getOriginalResource(); - this.aliases = aliases; - this.templates = templates; - this.catalogs = catalogs; } - } - static class CatalogRefOut { - public String name; - public String catalogName; - public String fullName; - public String catalogRef; - public String description; - public boolean importItems; + static class CatalogRefOut { + public String name; + public String catalogName; + public String fullName; + public String catalogRef; + public String description; + public boolean importItems; - public transient ResourceRef _catalogRef; - } + public transient ResourceRef _catalogRef; + } - private static CatalogRefOut getCatalogRefOut(String catalogName, dev.jbang.catalog.Catalog catalog, String name) { - CatalogRef ref = catalog.catalogs.get(name); - String catName = catalogName != null ? dev.jbang.catalog.Catalog.simplifyRef(catalogName) - : CatalogUtil.catalogRef(name); - String fullName = catalogName != null ? name + "@" + catName : name; - - CatalogRefOut out = new CatalogRefOut(); - out.name = name; - out.catalogName = catName; - out.fullName = fullName; - out.catalogRef = ref.catalogRef; - out.description = ref.description; - out.importItems = Boolean.TRUE.equals(ref.importItems); - out._catalogRef = ref.catalog.catalogRef; - return out; - } + private static CatalogRefOut getCatalogRefOut(String catalogName, dev.jbang.catalog.Catalog catalog, + String name) { + CatalogRef ref = catalog.catalogs.get(name); + String catName = catalogName != null ? dev.jbang.catalog.Catalog.simplifyRef(catalogName) + : CatalogUtil.catalogRef(name); + String fullName = catalogName != null ? name + "@" + catName : name; + + CatalogRefOut out = new CatalogRefOut(); + out.name = name; + out.catalogName = catName; + out.fullName = fullName; + out.catalogRef = ref.catalogRef; + out.description = ref.description; + out.importItems = Boolean.TRUE.equals(ref.importItems); + out._catalogRef = ref.catalog.catalogRef; + return out; + } - private static void printCatalogRef(PrintStream out, CatalogRefOut catalogRef, int indent) { - String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); - String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); - out.println(prefix1 + getColoredFullName(catalogRef.fullName) - + (catalogRef.importItems ? ConsoleOutput.magenta(" [importing]") : "")); - if (catalogRef.description != null) { - out.println(prefix2 + catalogRef.description); + private static void printCatalogRef(PrintStream out, CatalogRefOut catalogRef, int indent) { + String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); + String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); + out.println(prefix1 + getColoredFullName(catalogRef.fullName) + + (catalogRef.importItems ? ConsoleOutput.magenta(" [importing]") : "")); + if (catalogRef.description != null) { + out.println(prefix2 + catalogRef.description); + } + out.println(prefix2 + catalogRef.catalogRef); } - out.println(prefix2 + catalogRef.catalogRef); - } - static String getColoredFullName(String fullName) { - StringBuilder res = new StringBuilder(); - String[] parts = fullName.split("@"); - res.append(ConsoleOutput.yellow(parts[0])); - for (int i = 1; i < parts.length; i++) { - res.append(ConsoleOutput.cyan("@")); - res.append(parts[i]); + static String getColoredFullName(String fullName) { + StringBuilder res = new StringBuilder(); + String[] parts = fullName.split("@"); + res.append(ConsoleOutput.yellow(parts[0])); + for (int i = 1; i < parts.length; i++) { + res.append(ConsoleOutput.cyan("@")); + res.append(parts[i]); + } + return res.toString(); } - return res.toString(); } -} -@CommandLine.Command(name = "remove", description = "Remove existing catalog.") -class CatalogRemove extends BaseCatalogCommand { + @CommandDefinition(name = "remove", description = "Remove existing catalog.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class CatalogRemove extends BaseCatalogCommand { - @CommandLine.Parameters(paramLabel = "name", index = "0", description = "The name of the catalog", arity = "1") - String name; + @Argument(description = "The name of the catalog", required = true) + String name; - @Override - public Integer doCall() { - Path cat = getCatalog(true); - if (cat != null) { - CatalogUtil.removeCatalogRef(cat, name); - } else { - CatalogUtil.removeNearestCatalogRef(name); + @Override + public Integer doCall() throws IOException { + Path cat = catalogOptions.getCatalog(true); + if (cat != null) { + CatalogUtil.removeCatalogRef(cat, name); + } else { + CatalogUtil.removeNearestCatalogRef(name); + } + return EXIT_OK; } - return EXIT_OK; } } diff --git a/src/main/java/dev/jbang/cli/CatalogFileOptionsMixin.java b/src/main/java/dev/jbang/cli/CatalogFileOptionsMixin.java new file mode 100644 index 000000000..807970b5f --- /dev/null +++ b/src/main/java/dev/jbang/cli/CatalogFileOptionsMixin.java @@ -0,0 +1,48 @@ +package dev.jbang.cli; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.aesh.command.option.Option; + +import dev.jbang.Settings; +import dev.jbang.catalog.Catalog; + +public class CatalogFileOptionsMixin { + + @Option(shortName = 'g', name = "global", hasValue = false, description = "Use the global (user) catalog file") + boolean global; + + @Option(shortName = 'f', name = "file", description = "Path to the catalog file to use") + String catalogFile; + + public Path getCatalogOrDefault() { + Path cat = getCatalog(false); + return cat != null ? cat : Catalog.getCatalogFile(null); + } + + public Path getCatalog(boolean strict) { + Path cat; + if (global) { + cat = Settings.getUserCatalogFile(); + } else { + Path catPath = catalogFile != null ? Paths.get(catalogFile) : null; + if (catPath != null && Files.isDirectory(catPath)) { + Path defaultCatalog = catPath.resolve(Catalog.JBANG_CATALOG_JSON); + Path hiddenCatalog = catPath.resolve(Settings.JBANG_DOT_DIR).resolve(Catalog.JBANG_CATALOG_JSON); + if (!Files.exists(defaultCatalog) && Files.exists(hiddenCatalog)) { + cat = hiddenCatalog; + } else { + cat = defaultCatalog; + } + } else { + cat = catPath; + } + if (strict && cat != null && !Files.isRegularFile(cat)) { + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, "Catalog file not found at: " + catalogFile); + } + } + return cat; + } +} diff --git a/src/main/java/dev/jbang/cli/CommaSeparatedConverter.java b/src/main/java/dev/jbang/cli/CommaSeparatedConverter.java deleted file mode 100644 index b0072e6ae..000000000 --- a/src/main/java/dev/jbang/cli/CommaSeparatedConverter.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.jbang.cli; - -import java.util.Arrays; -import java.util.List; - -import picocli.CommandLine.ITypeConverter; - -public class CommaSeparatedConverter implements ITypeConverter> { - @Override - public List convert(final String input) throws Exception { - return Arrays.asList(input.split(",")); - } -} diff --git a/src/main/java/dev/jbang/cli/Completion.java b/src/main/java/dev/jbang/cli/Completion.java index 3bfb75089..566fc756f 100644 --- a/src/main/java/dev/jbang/cli/Completion.java +++ b/src/main/java/dev/jbang/cli/Completion.java @@ -1,25 +1,13 @@ package dev.jbang.cli; import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import picocli.AutoComplete; -import picocli.CommandLine; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.OptionSpec; +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Option; +import org.aesh.util.completer.ShellCompletionGenerator; +import org.aesh.util.completer.ShellCompletionGenerator.ShellType; -@CommandLine.Command(name = "completion", description = { - "Generate bash/zsh or fish completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.", - "Run the following command to give `${ROOT-COMMAND-NAME:-$PARENTCOMMAND}` TAB completion in the current shell:", - "", - " bash/zsh: source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})", - "", - " fish: eval (<(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --shell fish)" }) +@CommandDefinition(name = "completion", description = "Generate bash/zsh or fish completion script for jbang.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) public class Completion extends BaseCommand { @Override @@ -27,284 +15,34 @@ public Integer doCall() throws IOException { return completion(); } - @CommandLine.Option(names = { "-s", - "--shell" }, description = "The shell to generate the completion script for. Supported shells: bash (zsh) and fish") - private String shell = "bash"; + @Option(shortName = 's', name = "shell", description = "The shell to generate the completion script for. Supported shells: bash, zsh, and fish", defaultValue = "bash") + private String shell; public int completion() throws IOException { - - String script; - if (shell.equals("bash")) { - script = AutoComplete.bash( - spec.parent().name(), - spec.parent().commandLine()); - } else if (shell.equals("fish")) { - script = fish( - spec.parent().name(), - spec.parent().commandLine()); - } else { - throw new IllegalArgumentException("Unsupported shell: " + shell); + ShellType shellType; + switch (shell.toLowerCase()) { + case "bash": + shellType = ShellType.BASH; + break; + case "zsh": + shellType = ShellType.ZSH; + break; + case "fish": + shellType = ShellType.FISH; + break; + default: + throw new ExitException(EXIT_INVALID_INPUT, + "Unsupported shell: " + shell + ". Supported shells: bash, zsh, fish"); + } + + try { + String script = ShellCompletionGenerator.generateDynamic(shellType, JBang.class, "jbang"); + System.out.println(script); + } catch (Exception e) { + throw new ExitException(EXIT_INTERNAL_ERROR, "Failed to generate completion script: " + e.getMessage(), + e); } - // not PrintWriter.println: scripts with Windows line separators fail in strange - // ways! - - PrintStream out = System.out; - out.print(script); - out.print('\n'); - out.flush(); return EXIT_OK; } - - // copied from picocli fish PR https://github.com/remkop/picocli/pull/2463 - // once merged, we can remove this class and use picocli's own fish completion - - private static class CommandDescriptor { - final String functionName; - final String parentFunctionName; - final String parentWithoutTopLevelCommand; - final String commandName; - final CommandLine commandLine; - - CommandDescriptor(String functionName, String parentFunctionName, String parentWithoutTopLevelCommand, - String commandName, CommandLine commandLine) { - this.functionName = functionName; - this.parentFunctionName = parentFunctionName; - this.parentWithoutTopLevelCommand = parentWithoutTopLevelCommand; - this.commandName = commandName; - this.commandLine = commandLine; - } - } - - private static List createHierarchy(String scriptName, CommandLine commandLine) { - List result = new ArrayList(); - result.add(new CommandDescriptor("_picocli_" + scriptName, "", "", scriptName, commandLine)); - createSubHierarchy(scriptName, "", commandLine, result); - return result; - } - - private static String concat(String infix, String... values) { - return concat(infix, Arrays.asList(values)); - } - - private static String concat(String infix, List values) { - return concat(infix, values, null, new NullFunction()); - } - - private static class NullFunction implements Function { - public String apply(CharSequence value) { - return value.toString(); - } - } - - private static String concat(String infix, List values, T lastValue, - Function normalize) { - StringBuilder sb = new StringBuilder(); - for (T val : values) { - if (sb.length() > 0) { - sb.append(infix); - } - sb.append(normalize.apply(val)); - } - if (lastValue == null) { - return sb.toString(); - } - if (sb.length() > 0) { - sb.append(infix); - } - return sb.append(normalize.apply(lastValue)).toString(); - } - - private static void createSubHierarchy(String scriptName, String parentWithoutTopLevelCommand, - CommandLine commandLine, List out) { - // breadth-first: generate command lists and function calls for predecessors + - // each subcommand - for (Map.Entry entry : commandLine.getSubcommands().entrySet()) { - CommandSpec spec = entry.getValue().getCommandSpec(); - if (spec.usageMessage().hidden()) { - continue; - } // #887 skip hidden subcommands - String commandName = entry.getKey(); // may be an alias - String functionNameWithoutPrefix = bashify( - concat("_", parentWithoutTopLevelCommand.replace(' ', '_'), commandName)); - String functionName = concat("_", "_picocli", scriptName, functionNameWithoutPrefix); - String parentFunctionName = parentWithoutTopLevelCommand.length() == 0 - ? concat("_", "_picocli", scriptName) - : concat("_", "_picocli", scriptName, bashify(parentWithoutTopLevelCommand.replace(' ', '_'))); - - // remember the function name and associated subcommand so we can easily - // generate a function later - out.add(new CommandDescriptor(functionName, parentFunctionName, parentWithoutTopLevelCommand, commandName, - entry.getValue())); - } - - // then recursively do the same for all nested subcommands - for (Map.Entry entry : commandLine.getSubcommands().entrySet()) { - if (entry.getValue().getCommandSpec().usageMessage().hidden()) { - continue; - } // #887 skip hidden subcommands - String commandName = entry.getKey(); - String newParent = concat(" ", parentWithoutTopLevelCommand, commandName); - createSubHierarchy(scriptName, newParent, entry.getValue(), out); - } - } - - private static String bashify(CharSequence value) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (Character.isLetterOrDigit(c) || c == '_') { - builder.append(c); - } else if (Character.isSpaceChar(c)) { - builder.append('_'); - } - } - if (Character.isDigit(builder.charAt(0))) { // #2336 bash variables cannot start with a digit - builder.insert(0, "_"); - } - return builder.toString(); - } - - public static String fish(String scriptName, CommandLine commandLine) { - if (scriptName == null) { - throw new NullPointerException("scriptName"); - } - if (commandLine == null) { - throw new NullPointerException("commandLine"); - } - List hierarchy = createHierarchy(scriptName, commandLine); - StringBuilder result = new StringBuilder(); - - String parentFunction = ""; - List currentLevel = new ArrayList(); - List currentLevelCommands = new ArrayList(); - - CommandDescriptor rootDescriptor = null; - for (CommandDescriptor descriptor : hierarchy) { - if (descriptor.parentFunctionName.equals("")) { - rootDescriptor = descriptor; - parentFunction = descriptor.functionName; - continue; - } - if (!descriptor.parentFunctionName.equals(parentFunction)) { - processLevel(scriptName, result, currentLevel, currentLevelCommands, parentFunction, rootDescriptor); - rootDescriptor = null; - - currentLevel.clear(); - currentLevelCommands.clear(); - parentFunction = descriptor.parentFunctionName; - } - - currentLevel.add(descriptor); - currentLevelCommands.add(descriptor.commandName); - } - processLevel(scriptName, result, currentLevel, currentLevelCommands, parentFunction, rootDescriptor); - - return result.toString(); - } - - private static void processLevel(String scriptName, StringBuilder result, List currentLevel, - List currentLevelCommands, String levelName, - CommandDescriptor rootDescriptor) { - if (levelName.equals("")) { - levelName = "root"; - } - - // fish doesn't like dashes in variable names - levelName = levelName.replaceAll("-", "_"); - - result.append("\n# ").append(levelName).append(" completion\n"); - result.append("set -l ").append(levelName); - if (!currentLevelCommands.isEmpty()) { - result.append(" ").append(join(" ", currentLevelCommands)); - } - result.append("\n"); - if (rootDescriptor != null) { - String condition = " --condition \"not __fish_seen_subcommand_from $" + levelName + "\""; - for (OptionSpec optionSpec : rootDescriptor.commandLine.getCommandSpec().options()) { - completeFishOption(scriptName, optionSpec, condition, result); - } - } - for (CommandDescriptor commandDescriptor : currentLevel) { - - result.append("complete -c ").append(scriptName); - result.append(" --no-files"); // do not show files - result.append(" --condition \"not __fish_seen_subcommand_from $").append(levelName).append("\""); - if (!commandDescriptor.parentWithoutTopLevelCommand.equals("")) { - result.append(" --condition '__fish_seen_subcommand_from ") - .append( - commandDescriptor.parentWithoutTopLevelCommand) - .append("'"); - } - - result.append(" --arguments ").append(commandDescriptor.commandName); - - String[] descriptions = commandDescriptor.commandLine.getCommandSpec().usageMessage().description(); - String description = descriptions.length > 0 ? descriptions[0] : ""; - result.append(" -d '").append(sanitizeFishDescription(description)).append("'\n"); - - String condition = getFishCondition(commandDescriptor); - for (OptionSpec optionSpec : commandDescriptor.commandLine.getCommandSpec().options()) { - completeFishOption(scriptName, optionSpec, condition, result); - } - } - } - - private static String getFishCondition(CommandDescriptor commandDescriptor) { - StringBuilder condition = new StringBuilder(); - condition.append(" --condition \"__fish_seen_subcommand_from ") - .append(commandDescriptor.commandName) - .append("\""); - if (!commandDescriptor.parentWithoutTopLevelCommand.equals("")) { - condition.append(" --condition '__fish_seen_subcommand_from ") - .append( - commandDescriptor.parentWithoutTopLevelCommand) - .append("'"); - } - return condition.toString(); - } - - private static void completeFishOption(String scriptName, OptionSpec optionSpec, String conditions, - StringBuilder result) { - result.append("complete -c ").append(scriptName); - result.append(conditions); - result.append(" --long-option ").append(optionSpec.longestName().replace("--", "")); - - if (!optionSpec.shortestName().equals(optionSpec.longestName())) { - result.append(" --short-option ").append(optionSpec.shortestName().replace("-", "")); - } - - if (optionSpec.completionCandidates() != null) { - result.append(" --no-files --arguments '") - .append(join(" ", extract(optionSpec.completionCandidates()))) - .append("' "); - } - - String optionDescription = sanitizeFishDescription( - optionSpec.description().length > 0 ? optionSpec.description()[0] : ""); - result.append(" -d '").append(optionDescription).append("'\n"); - } - - private static List extract(Iterable generator) { - List result = new ArrayList(); - for (String e : generator) { - result.add(e); - } - return result; - } - - private static String sanitizeFishDescription(String description) { - return description.replace("'", "\\'"); - } - - private static String join(String delimeter, List list) { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < list.size(); i++) { - if (i > 0) { - result.append(delimeter); - } - result.append(list.get(i)); - } - return result.toString(); - } } diff --git a/src/main/java/dev/jbang/cli/Config.java b/src/main/java/dev/jbang/cli/Config.java index 961bed4f4..4b8c04c14 100644 --- a/src/main/java/dev/jbang/cli/Config.java +++ b/src/main/java/dev/jbang/cli/Config.java @@ -4,9 +4,16 @@ import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -16,15 +23,12 @@ import dev.jbang.util.ConsoleOutput; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "config", description = "Read and write configuration options.", subcommands = { - ConfigGet.class, ConfigSet.class, ConfigUnset.class, ConfigList.class -}) -public class Config { +@GroupCommandDefinition(name = "config", description = "Read and write configuration options.", groupCommands = { + Config.ConfigGet.class, Config.ConfigSet.class, + Config.ConfigUnset.class, Config.ConfigList.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class Config extends BaseCommand { - // IMPORTANT: These are options that can NOT be dynamically read from the - // PicoCLI configuration and have to maintained manually! Make sure to add + // IMPORTANT: These options have to be maintained manually! Make sure to add // an option for each configuration key that gets added! static AvailableOption[] extraOptions = { new AvailableOption(Settings.CONFIG_CACHE_EVICT, @@ -33,218 +37,225 @@ public class Config { "The timeout in milliseconds that will be used for any remote connections") }; - @CommandLine.Mixin - HelpMixin helpMixin; -} + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'config'"); + return EXIT_INVALID_INPUT; + } -abstract class BaseConfigCommand extends BaseCommand { + static abstract class BaseConfigCommand extends BaseCommand { - @CommandLine.Option(names = { "--global", "-g" }, description = "Use the global (user) config file") - boolean global; + @Option(shortName = 'g', name = "global", hasValue = false, description = "Use the global (user) config file") + boolean global; - @CommandLine.Option(names = { "--file", "-f" }, description = "Path to the config file to use") - Path configFile; + @Option(shortName = 'f', name = "file", description = "Path to the config file to use") + String configFile; - protected Configuration getConfig(Path cwd, boolean strict) { - Path cfgFile = getConfigFile(strict); - if (cfgFile != null) { - return Configuration.get(cfgFile); - } else { - return Configuration.getMerged(); + protected Configuration getConfig(Path cwd, boolean strict) { + Path cfgFile = getConfigFile(strict); + if (cfgFile != null) { + return Configuration.get(cfgFile); + } else { + return Configuration.getMerged(); + } } - } - protected Path getConfigFile(boolean strict) { - Path cfg; - if (global) { - cfg = Settings.getUserConfigFile(); - } else { - if (configFile != null && Files.isDirectory(configFile)) { - Path defaultConfig = configFile.resolve(Configuration.JBANG_CONFIG_PROPS); - Path hiddenConfig = configFile.resolve(Settings.JBANG_DOT_DIR) - .resolve(Configuration.JBANG_CONFIG_PROPS); - if (!Files.exists(defaultConfig) && Files.exists(hiddenConfig)) { - cfg = hiddenConfig; + protected Path getConfigFile(boolean strict) { + Path cfg; + if (global) { + cfg = Settings.getUserConfigFile(); + } else { + Path cfgPath = configFile != null ? Paths.get(configFile) : null; + if (cfgPath != null && Files.isDirectory(cfgPath)) { + Path defaultConfig = cfgPath.resolve(Configuration.JBANG_CONFIG_PROPS); + Path hiddenConfig = cfgPath.resolve(Settings.JBANG_DOT_DIR) + .resolve(Configuration.JBANG_CONFIG_PROPS); + if (!Files.exists(defaultConfig) && Files.exists(hiddenConfig)) { + cfg = hiddenConfig; + } else { + cfg = defaultConfig; + } } else { - cfg = defaultConfig; + cfg = cfgPath; + } + if (strict && cfg != null && !Files.isRegularFile(cfg)) { + throw new ExitException(EXIT_INVALID_INPUT, "Config file not found at: " + configFile); } - } else { - cfg = configFile; - } - if (strict && cfg != null && !Files.isRegularFile(cfg)) { - throw new IllegalArgumentException("Config file not found at: " + configFile); } - } - return cfg; - } -} - -@CommandLine.Command(name = "get", description = "Get a configuration value") -class ConfigGet extends BaseConfigCommand { - @CommandLine.Parameters(index = "0", arity = "1", description = "The name of the configuration option to get") - String key; - - @Override - public Integer doCall() { - Configuration cfg = getConfig(null, false); - if (cfg.containsKey(key)) { - String res = Objects.toString(cfg.get(key)); - System.out.println(res); - return EXIT_OK; - } else { - Util.infoMsg("No configuration option found with that name: " + key); - return EXIT_INVALID_INPUT; + return cfg; } } -} - -@CommandLine.Command(name = "set", description = "Set a configuration value") -class ConfigSet extends BaseConfigCommand { - @CommandLine.Parameters(index = "0", arity = "1", description = "The name of the configuration option to set") - String key; - @CommandLine.Parameters(index = "1", arity = "1", description = "The value to set for the configuration option") - String value; - - @Override - public Integer doCall() throws IOException { - Path cfgFile = getConfigFile(false); - if (cfgFile != null) { - ConfigUtil.setConfigValue(cfgFile, key, value); - } else { - cfgFile = ConfigUtil.setNearestConfigValue(key, value); + @CommandDefinition(name = "get", description = "Get a configuration value", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ConfigGet extends BaseConfigCommand { + @Argument(description = "The name of the configuration option to get", required = true) + String key; + + @Override + public Integer doCall() throws IOException { + Configuration cfg = getConfig(null, false); + if (cfg.containsKey(key)) { + String res = Objects.toString(cfg.get(key)); + System.out.println(res); + return EXIT_OK; + } else { + Util.infoMsg("No configuration option found with that name: " + key); + return EXIT_INVALID_INPUT; + } } - Util.infoMsg("Option '" + key + "' set to '" + value + "' in " + cfgFile); - return EXIT_OK; } -} -@CommandLine.Command(name = "unset", description = "Remove a configuration value") -class ConfigUnset extends BaseConfigCommand { - @CommandLine.Parameters(index = "0", arity = "1", description = "The name of the configuration option to set") - String key; + @CommandDefinition(name = "set", description = "Set a configuration value", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ConfigSet extends BaseConfigCommand { + @Arguments(description = "The key and value to set (either 'key value' or 'key=value')", required = true) + List args; + + @Override + public Integer doCall() throws IOException { + String key; + String value; + if (args == null || args.isEmpty()) { + throw new ExitException(EXIT_INVALID_INPUT, "Missing required arguments: key and value"); + } + if (args.size() == 1) { + int eqIdx = args.get(0).indexOf('='); + if (eqIdx >= 0) { + key = args.get(0).substring(0, eqIdx); + value = args.get(0).substring(eqIdx + 1); + } else { + throw new ExitException(EXIT_INVALID_INPUT, "Expected key=value format or separate key and value arguments"); + } + } else { + key = args.get(0); + value = args.get(1); + } - @Override - public Integer doCall() throws IOException { - Configuration cfg = getConfig(null, false); - if (cfg.containsKey(key)) { Path cfgFile = getConfigFile(false); if (cfgFile != null) { - ConfigUtil.unsetConfigValue(cfgFile, key); + ConfigUtil.setConfigValue(cfgFile, key, value); } else { - cfgFile = ConfigUtil.unsetNearestConfigValue(key); + cfgFile = ConfigUtil.setNearestConfigValue(key, value); } - if (cfgFile != null) { - Util.infoMsg("Option '" + key + "' removed from in " + cfgFile); + Util.infoMsg("Option '" + key + "' set to '" + value + "' in " + cfgFile); + return EXIT_OK; + } + } + + @CommandDefinition(name = "unset", description = "Remove a configuration value", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ConfigUnset extends BaseConfigCommand { + @Argument(description = "The name of the configuration option to remove", required = true) + String key; + + @Override + public Integer doCall() throws IOException { + Configuration cfg = getConfig(null, false); + if (cfg.containsKey(key)) { + Path cfgFile = getConfigFile(false); + if (cfgFile != null) { + ConfigUtil.unsetConfigValue(cfgFile, key); + } else { + cfgFile = ConfigUtil.unsetNearestConfigValue(key); + } + if (cfgFile != null) { + Util.infoMsg("Option '" + key + "' removed from in " + cfgFile); + } else { + Util.warnMsg("Cannot remove built-in option '" + key + "'"); + } + return EXIT_OK; } else { - Util.warnMsg("Cannot remove built-in option '" + key + "'"); + Util.infoMsg("No configuration option found with that name: " + key); + return EXIT_INVALID_INPUT; } - return EXIT_OK; - } else { - Util.infoMsg("No configuration option found with that name: " + key); - return EXIT_INVALID_INPUT; } } -} -@CommandLine.Command(name = "list", description = "List active configuration values") -class ConfigList extends BaseConfigCommand { - @CommandLine.Option(names = { - "--show-origin" }, description = "Show the origin of the configuration") - boolean showOrigin; + @CommandDefinition(name = "list", description = "List active configuration values", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ConfigList extends BaseConfigCommand { + @Option(name = "show-origin", hasValue = false, description = "Show the origin of the configuration") + boolean showOrigin; - @CommandLine.Option(names = { - "--show-available" }, description = "Show the available key names") - boolean showAvailable; + @Option(name = "show-available", hasValue = false, description = "Show the available key names") + boolean showAvailable; - @CommandLine.Mixin - FormatMixin formatMixin; + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; - @Override - public Integer doCall() throws IOException { - PrintStream out = System.out; - if (showAvailable && showOrigin) { - throw new IllegalArgumentException( - "Options '--show-available' and '--show-origin' cannot be used together"); - } - if (showAvailable) { - Set opts = new HashSet<>(Arrays.asList(Config.extraOptions)); - gatherKeys(JBang.getCommandLine(), opts); - if (formatMixin.format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(opts.stream().sorted().collect(Collectors.toList()), out); - } else { - opts.stream().sorted().forEach(opt -> { - out.print(ConsoleOutput.yellow(opt.key)); - out.print(" = "); - out.println(opt.description); - }); + @Override + public Integer doCall() throws IOException { + PrintStream out = System.out; + if (showAvailable && showOrigin) { + throw new ExitException(EXIT_INVALID_INPUT, + "Options '--show-available' and '--show-origin' cannot be used together"); } - } else { - Configuration cfg = getConfig(null, true); - if (showOrigin) { - printConfigWithOrigin(out, cfg, formatMixin.format); + if (showAvailable) { + Set opts = new HashSet<>(Arrays.asList(Config.extraOptions)); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(opts.stream().sorted().collect(Collectors.toList()), out); + } else { + opts.stream().sorted().forEach(opt -> { + out.print(ConsoleOutput.yellow(opt.key)); + out.print(" = "); + out.println(opt.description); + }); + } } else { - printConfig(out, cfg, formatMixin.format); + Configuration cfg = getConfig(null, true); + if (showOrigin) { + printConfigWithOrigin(out, cfg, format); + } else { + printConfig(out, cfg, format); + } } + return EXIT_OK; } - return EXIT_OK; - } - private void gatherKeys(CommandLine cmd, Set keys) { - for (CommandLine c : cmd.getCommandSpec().subcommands().values()) { - gatherKeys(c, keys); - } - for (CommandLine.Model.OptionSpec opt : cmd.getCommandSpec().options()) { - keys.add(new AvailableOption(JBang.argSpecKey(opt), String.join(" ", opt.description()))); + private void printConfig(PrintStream out, Configuration cfg, String format) { + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(cfg.asMap(), out); + } else { + cfg.flatten() + .keySet() + .stream() + .sorted() + .forEach(key -> out.println(ConsoleOutput.yellow(key) + " = " + cfg.get(key))); + } } - } - private void printConfig(PrintStream out, Configuration cfg, FormatMixin.Format format) { - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(cfg.asMap(), out); - } else { - cfg.flatten() - .keySet() - .stream() - .sorted() - .forEach(key -> out.println(ConsoleOutput.yellow(key) + " = " + cfg.get(key))); + static class OriginOut { + String resourceRef; + Map properties; } - } - static class OriginOut { - String resourceRef; - Map properties; - } - - private void printConfigWithOrigin(PrintStream out, Configuration cfg, FormatMixin.Format format) { - List orgs = new ArrayList<>(); - while (cfg != null) { - OriginOut org = new OriginOut(); - org.resourceRef = cfg.getStoreRef().getOriginalResource(); - org.properties = cfg.asMap(); - orgs.add(org); - cfg = cfg.getFallback(); - } + private void printConfigWithOrigin(PrintStream out, Configuration cfg, String format) { + List orgs = new ArrayList<>(); + while (cfg != null) { + OriginOut org = new OriginOut(); + org.resourceRef = cfg.getStoreRef().getOriginalResource(); + org.properties = cfg.asMap(); + orgs.add(org); + cfg = cfg.getFallback(); + } - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(orgs, out); - } else { - Set printedKeys = new HashSet<>(); - for (OriginOut org : orgs) { - Set keysToPrint = org.properties.keySet() - .stream() - .filter(key -> !printedKeys.contains(key)) - .collect(Collectors.toSet()); - if (!keysToPrint.isEmpty()) { - out.println(ConsoleOutput.bold(org.resourceRef)); - keysToPrint.stream() - .sorted() - .forEach(key -> out.println( - " " + ConsoleOutput.yellow(key) + " = " + org.properties.get(key))); - printedKeys.addAll(keysToPrint); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(orgs, out); + } else { + Set printedKeys = new HashSet<>(); + for (OriginOut org : orgs) { + Set keysToPrint = org.properties.keySet() + .stream() + .filter(key -> !printedKeys.contains(key)) + .collect(Collectors.toSet()); + if (!keysToPrint.isEmpty()) { + out.println(ConsoleOutput.bold(org.resourceRef)); + keysToPrint.stream() + .sorted() + .forEach(key -> out.println( + " " + ConsoleOutput.yellow(key) + " = " + org.properties.get(key))); + printedKeys.addAll(keysToPrint); + } } } } diff --git a/src/main/java/dev/jbang/cli/DebugOptionParser.java b/src/main/java/dev/jbang/cli/DebugOptionParser.java new file mode 100644 index 000000000..3573675fc --- /dev/null +++ b/src/main/java/dev/jbang/cli/DebugOptionParser.java @@ -0,0 +1,64 @@ +package dev.jbang.cli; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.aesh.command.impl.internal.ProcessedOption; +import org.aesh.command.parser.OptionParser; +import org.aesh.command.parser.OptionParserException; +import org.aesh.parser.ParsedLineIterator; + +public class DebugOptionParser implements OptionParser { + + private static final Pattern DEBUG_VALUE_PATTERN = Pattern + .compile("(?:(.*?:)?(\\d+\\??))|(?:\\S*=\\S+\\??)"); + + @Override + public void parse(ParsedLineIterator iter, ProcessedOption option) throws OptionParserException { + String word = iter.peekWord(); + + String prefix, optName; + if (option.isLongNameUsed()) { + prefix = "--"; + optName = option.name(); + } else { + prefix = "-"; + optName = option.shortName(); + } + + String fullPrefix = prefix + optName; + + if (word.startsWith(fullPrefix + "=")) { + String value = word.substring(fullPrefix.length() + 1); + addOptionValue(option, value); + iter.pollParsedWord(); + return; + } + + iter.pollParsedWord(); + + if (iter.hasNextWord()) { + String nextWord = iter.peekWord(); + if (!nextWord.startsWith("-")) { + Matcher m = DEBUG_VALUE_PATTERN.matcher(nextWord); + if (m.matches()) { + addOptionValue(option, nextWord); + iter.pollParsedWord(); + return; + } + } + } + + addOptionValue(option, ""); + } + + private void addOptionValue(ProcessedOption option, String value) { + if (!option.getValues().isEmpty()) { + String existing = String.join(",", option.getValues()); + option.getValues().clear(); + option.addValue(existing + "," + value); + } else { + option.addValue(value); + } + } +} diff --git a/src/main/java/dev/jbang/cli/DependencyInfoMixin.java b/src/main/java/dev/jbang/cli/DependencyInfoMixin.java index 32d175b5f..4dd46729b 100644 --- a/src/main/java/dev/jbang/cli/DependencyInfoMixin.java +++ b/src/main/java/dev/jbang/cli/DependencyInfoMixin.java @@ -4,27 +4,31 @@ import java.util.List; import java.util.Map; -import dev.jbang.util.Util; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionGroup; +import org.aesh.command.option.OptionList; -import picocli.CommandLine; +import dev.jbang.util.Util; public class DependencyInfoMixin { - @CommandLine.Option(names = { "-D" }, description = "set a system property", mapFallbackValue = "true") + + @OptionGroup(shortName = 'D', description = "set a system property", defaultValue = "true") Map properties; - @CommandLine.Option(names = { - "--deps" }, converter = CommaSeparatedConverter.class, description = "Add additional dependencies (Use commas to separate them).") + + @OptionList(name = "deps", valueSeparator = ',', description = "Add additional dependencies (Use commas to separate them).") List dependencies; - @CommandLine.Option(names = { - "--repos" }, converter = CommaSeparatedConverter.class, description = "Add additional repositories.") + + @OptionList(name = "repos", valueSeparator = ',', description = "Add additional repositories.") List repositories; - @CommandLine.Option(names = { "--cp", "--class-path" }, description = "Add class path entries.") + + @OptionList(name = "cp", aliases = { "class-path" }, description = "Add class path entries.") List classpaths; - @CommandLine.Option(names = { - "--ignore-transitive-repositories", - "--itr" }, description = "Ignore remote repositories found in transitive dependencies") - void setIgnoreTransitiveRepositories(boolean ignoreTransitiveRepositories) { - Util.setIgnoreTransitiveRepositories(ignoreTransitiveRepositories); + @Option(name = "ignore-transitive-repositories", hasValue = false, description = "Ignore remote repositories found in transitive dependencies") + boolean ignoreTransitiveRepositories; + + public Map getProperties() { + return properties != null && properties.isEmpty() ? null : properties; } public List getDependencies() { @@ -39,8 +43,10 @@ public List getClasspaths() { return classpaths; } - public Map getProperties() { - return properties; + public void applyIgnoreTransitiveRepositories() { + if (ignoreTransitiveRepositories) { + Util.setIgnoreTransitiveRepositories(true); + } } public List opts() { diff --git a/src/main/java/dev/jbang/cli/DeprecatedMessageHandler.java b/src/main/java/dev/jbang/cli/DeprecatedMessageHandler.java deleted file mode 100644 index 90da7f8f6..000000000 --- a/src/main/java/dev/jbang/cli/DeprecatedMessageHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.jbang.cli; - -import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; - -import picocli.CommandLine; -import picocli.CommandLine.UnmatchedArgumentException; - -public class DeprecatedMessageHandler implements CommandLine.IParameterExceptionHandler { - private final CommandLine.IParameterExceptionHandler delegate; - - public DeprecatedMessageHandler(CommandLine.IParameterExceptionHandler parameterExceptionHandler) { - this.delegate = parameterExceptionHandler; - } - - static Map oldFlags = new HashMap() { - { - put("--alias", "jbang alias --help"); - put("--init", "jbang init --help"); - put("--edit", "jbang edit --help"); - put("--edit-live", "jbang edit --help"); - put("--trust", "jbang trust --help"); - } - }; - - @Override - public int handleParseException(CommandLine.ParameterException ex, String[] args) throws Exception { - - if (ex instanceof UnmatchedArgumentException) { - CommandLine cmd = ex.getCommandLine(); - PrintWriter writer = cmd.getErr(); - - UnmatchedArgumentException uae = (UnmatchedArgumentException) ex; - String s = uae.getUnmatched().get(0); - if (s.contains("=")) { - s = s.substring(0, s.indexOf("=")); - } - - if (oldFlags.containsKey(s)) { - writer.printf("%s is a deprecated and now removed flag. See " + oldFlags.get(s) - + " for more details on its replacement.\n", s); - } - } - return delegate.handleParseException(ex, args); - } -} diff --git a/src/main/java/dev/jbang/cli/Deps.java b/src/main/java/dev/jbang/cli/Deps.java index d5967d7e1..2dff3515f 100644 --- a/src/main/java/dev/jbang/cli/Deps.java +++ b/src/main/java/dev/jbang/cli/Deps.java @@ -8,8 +8,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; import org.eclipse.aether.artifact.Artifact; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; @@ -19,150 +22,132 @@ import dev.jbang.source.update.FileUpdateStrategy; import dev.jbang.source.update.FileUpdaters; -import picocli.CommandLine; -import picocli.CommandLine.Command; - -@Command(name = "deps", description = "Manage dependencies in jbang files.", subcommands = { DepsAdd.class, - DepsSearch.class }) +@GroupCommandDefinition(name = "deps", description = "Manage dependencies in jbang files.", groupCommands = { + Deps.DepsSearch.class, Deps.DepsAdd.class }, generateHelp = true, helpGroup = "Editing", defaultValueProvider = JBangDefaultValueProvider.class) public class Deps extends BaseCommand { @Override public Integer doCall() throws IOException { - // This is a parent command, subcommands handle the actual work - return EXIT_OK; + System.err.println("Missing required subcommand for 'deps'"); + return EXIT_INVALID_INPUT; } -} -@Command(name = "search", header = "Search for artifacts in local and central Maven repositories.", description = { - "", - " ${COMMAND-FULL-NAME}", - " (to search for artifacts interactively)", - " or ${COMMAND-FULL-NAME} jash", - " (to search for 'jash' interactively)", - " or ${COMMAND-FULL-NAME} myapp.java", - " (to search for dependencies and add them to myapp.java)", - " or ${COMMAND-FULL-NAME} jash myapp.java", - " (to search initially for 'jash' and add dependency to myapp.java)", - "", - " note: JBang will detect if the arguments are a file or query, but if you want to be explicit you can use the --query and --target options.", - "", - "" }) -class DepsSearch extends BaseCommand { - - @CommandLine.Option(names = "--max", description = "Maximum number of results to return.", defaultValue = "100") - int max; - - @CommandLine.Option(names = { "--query", - "-q" }, description = "Artifact pattern to search for.", arity = "0..1") - Optional query; - - @CommandLine.Option(names = { - "--target" }, description = "Target where to add the dependency, i.e. app.java or build.jbang", arity = "0..1") - Optional target; - - @CommandLine.Parameters(arity = "0..2", paramLabel = "[query] [target]", description = "Artifact pattern to search for and target where to add the dependency, i.e. app.java or build.jbang. Query and target can both be optional") - List args = new ArrayList<>(); - - private boolean looksLikeATarget(String a0) { - return Files.exists(Paths.get(a0)); - } + @CommandDefinition(name = "search", description = "Search for artifacts in local and central Maven repositories.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class DepsSearch extends BaseCommand { - private void updateTarget(String a0) throws IOException { - if (target.isPresent()) { - throw new IllegalArgumentException("Cannot provide both target as as parameter and as option"); - } else { - target = Optional.of(Paths.get(a0)); - } - } + @Option(name = "max", description = "Maximum number of results to return.", defaultValue = "100") + int max; - private void updateQuery(String a0) { - if (query.isPresent()) { - throw new IllegalArgumentException("Cannot provide both query as as parameter and as option"); - } else { - query = Optional.of(a0); - } - } + @Option(shortName = 'q', name = "query", description = "Artifact pattern to search for.") + String query; - @Override - public Integer doCall() throws IOException { + @Option(name = "target", description = "Target where to add the dependency, i.e. app.java or build.jbang") + String target; + + @Arguments(description = "[query] [target]") + List args = new ArrayList<>(); - if (args.size() == 1) { // either query or target - String a0 = args.get(0); - if (looksLikeATarget(a0)) { - updateTarget(a0); + private boolean looksLikeATarget(String a0) { + return Files.exists(Paths.get(a0)); + } + + private void updateTarget(String a0) throws IOException { + if (target != null) { + throw new ExitException(EXIT_INVALID_INPUT, "Cannot provide both target as as parameter and as option"); } else { - updateQuery(a0); + target = a0; } - } else if (args.size() == 2) { // both query and target - updateQuery(args.get(0)); - updateTarget(args.get(1)); } - if (target.isPresent() && !Files.exists(target.get())) { - throw new ExitException(EXIT_INVALID_INPUT, "Target file does not exist: " + target.get()); + private void updateQuery(String a0) { + if (query != null) { + throw new ExitException(EXIT_INVALID_INPUT, "Cannot provide both query as as parameter and as option"); + } else { + query = a0; + } } - try (Terminal terminal = TerminalBuilder.builder().system(true).build()) { - try { - Artifact artifact = new ArtifactSearchWidget(terminal).search(query.orElse("")); - if (target.isPresent()) { - DepsAdd.updateFile(target.get(), Collections.singletonList(artifactGav(artifact))); - info("Added " + artifactGav(artifact) + " to " + target.get()); + @Override + public Integer doCall() throws IOException { + + if (args.size() == 1) { // either query or target + String a0 = args.get(0); + if (looksLikeATarget(a0)) { + updateTarget(a0); } else { - System.out.printf("%s:%s:%s%n", artifact.getGroupId(), artifact.getArtifactId(), - artifact.getVersion()); + updateQuery(a0); + } + } else if (args.size() == 2) { // both query and target + updateQuery(args.get(0)); + updateTarget(args.get(1)); + } + + Path targetPath = target != null ? Paths.get(target) : null; + if (targetPath != null && !Files.exists(targetPath)) { + throw new ExitException(EXIT_INVALID_INPUT, "Target file does not exist: " + targetPath); + } + + try (Terminal terminal = TerminalBuilder.builder().system(true).build()) { + try { + Artifact artifact = new ArtifactSearchWidget(terminal).search(query != null ? query : ""); + if (targetPath != null) { + DepsAdd.updateFile(targetPath, Collections.singletonList(artifactGav(artifact))); + info("Added " + artifactGav(artifact) + " to " + targetPath); + } else { + System.out.printf("%s:%s:%s%n", artifact.getGroupId(), artifact.getArtifactId(), + artifact.getVersion()); + } + return EXIT_OK; + } catch (IOError e) { + return EXIT_INVALID_INPUT; } - return EXIT_OK; - } catch (IOError e) { - return EXIT_INVALID_INPUT; } } - } - private static String artifactGav(Artifact artifact) { - return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(); + private static String artifactGav(Artifact artifact) { + return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(); + } } -} - -@Command(name = "add", description = "Add dependencies to a jbang file.") -class DepsAdd extends BaseCommand { + @CommandDefinition(name = "add", description = "Add dependencies to a jbang file.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class DepsAdd extends BaseCommand { - @CommandLine.Parameters(description = "Dependencies to add (groupId:artifactId:version) and target file (.java or build.jbang)") - List parameters = new ArrayList<>(); + @Arguments(description = "Dependencies to add (groupId:artifactId:version) and target file (.java or build.jbang)") + List parameters = new ArrayList<>(); - @Override - public Integer doCall() throws IOException { - if (parameters.size() < 2) { - throw new ExitException(EXIT_INVALID_INPUT, "At least one dependency and target file are required"); - } + @Override + public Integer doCall() throws IOException { + if (parameters.size() < 2) { + throw new ExitException(EXIT_INVALID_INPUT, "At least one dependency and target file are required"); + } - // Last parameter is the target file - Path targetFile = Paths.get(parameters.get(parameters.size() - 1)); - List dependencies = parameters.subList(0, parameters.size() - 1); + // Last parameter is the target file + Path targetFile = Paths.get(parameters.get(parameters.size() - 1)); + List dependencies = parameters.subList(0, parameters.size() - 1); - if (!Files.exists(targetFile)) { - throw new ExitException(EXIT_INVALID_INPUT, "Target file does not exist: " + targetFile); - } + if (!Files.exists(targetFile)) { + throw new ExitException(EXIT_INVALID_INPUT, "Target file does not exist: " + targetFile); + } - updateFile(targetFile, dependencies); + updateFile(targetFile, dependencies); - info("Added dependencies to " + targetFile); - return EXIT_OK; - } + info("Added dependencies to " + targetFile); + return EXIT_OK; + } - static public void updateFile(Path file, List dependencies) throws IOException { - // Validate dependencies - for (String dep : dependencies) { - if (!DependencyUtil.looksLikeAGav(dep)) { - throw new ExitException(EXIT_INVALID_INPUT, "Invalid dependency format: " + dep); + static public void updateFile(Path file, List dependencies) throws IOException { + // Validate dependencies + for (String dep : dependencies) { + if (!DependencyUtil.looksLikeAGav(dep)) { + throw new ExitException(EXIT_INVALID_INPUT, "Invalid dependency format: " + dep); + } + } + if (!Files.exists(file)) { + throw new ExitException(EXIT_INVALID_INPUT, "File does not exist: " + file); } - } - if (!Files.exists(file)) { - throw new ExitException(EXIT_INVALID_INPUT, "File does not exist: " + file); - } - FileUpdateStrategy strategy = FileUpdaters.forFile(file); - strategy.updateFile(file, dependencies); + FileUpdateStrategy strategy = FileUpdaters.forFile(file); + strategy.updateFile(file, dependencies); + } } } diff --git a/src/main/java/dev/jbang/cli/Edit.java b/src/main/java/dev/jbang/cli/Edit.java index 7e9e9382c..a218927c3 100644 --- a/src/main/java/dev/jbang/cli/Edit.java +++ b/src/main/java/dev/jbang/cli/Edit.java @@ -15,6 +15,12 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import dev.jbang.Cache; import dev.jbang.Settings; import dev.jbang.dependencies.DependencyUtil; @@ -29,35 +35,56 @@ import dev.jbang.util.Util.Shell; import io.quarkus.qute.Template; -import picocli.CommandLine; -@CommandLine.Command(name = "edit", description = "Setup a temporary project to edit script in an IDE.") +@CommandDefinition(name = "edit", description = "Setup a temporary project to edit script in an IDE.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class, helpGroup = "Editing") public class Edit extends BaseCommand { static String[] knownEditors = { "codium", "code", "cursor", "eclipse", "idea", "netbeans" }; - @CommandLine.Mixin + @Mixin ScriptMixin scriptMixin; - @CommandLine.Mixin + @Mixin DependencyInfoMixin dependencyInfoMixin; - @CommandLine.Parameters(index = "1", arity = "0..N") - List additionalFiles = new ArrayList<>(); + @Argument(description = "A file or URL to a Java code file") + String scriptArg; + + @Arguments(description = "Additional files") + List additionalFiles; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; + } + dependencyInfoMixin.applyIgnoreTransitiveRepositories(); + applyEditorDefaults(); + } - @CommandLine.Option(names = { - "--live" }, description = "Open directory in IDE's that support JBang or generate temporary project with option to regenerate project on dependency changes.") + private void applyEditorDefaults() { + String envEditor = System.getenv("JBANG_EDITOR"); + if (envEditor != null && !envEditor.isEmpty()) { + editor = envEditor; + } else if (editor != null && editor.isEmpty()) { + String cfgEditor = dev.jbang.Configuration.instance().get("edit.open"); + if (cfgEditor != null) { + editor = cfgEditor; + } + } + } + + @Option(name = "live", hasValue = false, description = "Open directory in IDE's that support JBang or generate temporary project with option to regenerate project on dependency changes.") public boolean live; - @CommandLine.Option(names = { - "--open" }, arity = "0..1", defaultValue = "${JBANG_EDITOR:-${default.edit.open:-}}", fallbackValue = "${JBANG_EDITOR:-${default.edit.open:-}}", description = "Opens editor/IDE on the temporary project.", preprocessor = StrictParameterPreprocessor.class) - public Optional editor; + @Option(name = "open", parser = StrictOptionParser.class, description = "Opens editor/IDE on the temporary project.") + public String editor; - @CommandLine.Option(names = { "--no-open" }, description = "Explicitly prevent JBang from opening an editor/IDE") + @Option(name = "no-open", hasValue = false, description = "Explicitly prevent JBang from opening an editor/IDE") public boolean noOpen; - @CommandLine.Option(names = { "-b", - "--sandbox" }, description = "Edit in sandbox mode. Useful when the editor/IDE used has no JBang support") + @Option(shortName = 'b', name = "sandbox", hasValue = false, description = "Edit in sandbox mode. Useful when the editor/IDE used has no JBang support") boolean sandbox; /** @@ -133,10 +160,13 @@ static public Path locateProjectDir(Path location) { @Override public Integer doCall() throws IOException { - // force download sources when editing Util.setDownloadSources(true); + if (additionalFiles == null) { + additionalFiles = new ArrayList<>(); + } + if (!sandbox) { File location; if (scriptMixin.scriptOrFile != null) { @@ -173,14 +203,13 @@ public Integer doCall() throws IOException { Path project = createProjectForLinkedEdit(prj, Collections.emptyList(), false); String projectPathString = pathToString(project.toAbsolutePath()); - // err.println(project.getAbsolutePath()); if (!noOpen) { openEditor(projectPathString, additionalFiles); } if (!live) { - out.println(projectPathString); // quit(project.getAbsolutePath()); + out.println(projectPathString); } else { Path orginalFile = prj.getResourceRef().getFile(); if (!Files.exists(orginalFile)) { @@ -243,20 +272,21 @@ private void watchForChanges(Path orginalFile, Callable action) throws I // try open editor if possible and install if needed, returns true if editor // started, false if not possible (i.e. editor not available) private boolean openEditor(String projectPathString, List additionalFiles) throws IOException { - if (!editor.isPresent() || editor.get().isEmpty()) { - editor = askEditor(); - if (!editor.isPresent()) { + if (editor == null || editor.isEmpty()) { + Optional ed = askEditor(); + if (!ed.isPresent()) { return false; } + editor = ed.get(); } else { - showStartingMsg(editor.get(), !editor.get().equals(spec.findOption("open").defaultValue())); + showStartingMsg(editor, true); } - if ("gitpod".equals(editor.get()) && System.getenv("GITPOD_WORKSPACE_URL") != null) { + if ("gitpod".equals(editor) && System.getenv("GITPOD_WORKSPACE_URL") != null) { info("Open this url to edit the project in your gitpod session:\n\n" + System.getenv("GITPOD_WORKSPACE_URL") + "#" + projectPathString + "\n\n"); } else { List optionList = new ArrayList<>(); - optionList.add(editor.get()); + optionList.add(editor); optionList.add(projectPathString); optionList.addAll(additionalFiles); @@ -283,7 +313,7 @@ ProjectBuilder createProjectBuilder() { .additionalClasspaths(dependencyInfoMixin.getClasspaths()) .additionalSources(scriptMixin.sources) .additionalResources(scriptMixin.resources) - .forceType(scriptMixin.forceType); + .forceType(scriptMixin.getForceType()); } private static Optional askEditor() throws IOException { @@ -530,21 +560,6 @@ Path createProjectForLinkedEdit(Project prj, List arguments, boolean rel destination); } - // setup intellij - disabled for now as idea was not picking these up directly - /* - * templateName = "idea-port-4004.qute.xml"; destination = new - * File(tmpProjectDir, ".idea/runConfigurations/" + baseName + - * "-port-4004.xml").toPath(); destination.toFile().getParentFile().mkdirs(); - * renderTemplate(engine, collectDependencies, baseName, resolvedDependencies, - * templateName, script.getArguments(), destination); - * - * templateName = "idea.qute.xml"; destination = new File(tmpProjectDir, - * ".idea/runConfigurations/" + baseName + ".xml").toPath(); - * destination.toFile().getParentFile().mkdirs(); renderTemplate(engine, - * collectDependencies, baseName, resolvedDependencies, templateName, - * script.getArguments(), destination); - */ - return tmpProjectDir; } @@ -596,5 +611,4 @@ static String stripPrefix(String fileName) { return fileName; } } - } diff --git a/src/main/java/dev/jbang/cli/Export.java b/src/main/java/dev/jbang/cli/Export.java index e41262d9a..4ee3b9272 100644 --- a/src/main/java/dev/jbang/cli/Export.java +++ b/src/main/java/dev/jbang/cli/Export.java @@ -15,6 +15,11 @@ import java.util.jar.Manifest; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; @@ -33,813 +38,872 @@ import dev.jbang.util.Util; import io.quarkus.qute.Template; -import picocli.CommandLine; -import picocli.CommandLine.Command; - -@Command(name = "export", description = "Export the result of a build or the set of the sources to a project.", subcommands = { - ExportPortable.class, - ExportLocal.class, ExportMavenPublish.class, ExportNative.class, ExportFatjar.class, ExportJlink.class, - ExportGradleProject.class, ExportMavenProject.class }) -public class Export { - @CommandLine.Mixin - HelpMixin helpMixin; -} - -abstract class BaseExportCommand extends BaseCommand { - @CommandLine.Mixin - ExportMixin exportMixin; - - protected Manifest createManifest(String newPath) { - // Create a Manifest with a Class-Path option - Manifest mf = new Manifest(); - // without MANIFEST_VERSION nothing is saved. - mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - mf.getMainAttributes().putValue(Attributes.Name.CLASS_PATH.toString(), newPath); - return mf; - } +@GroupCommandDefinition(name = "export", description = "Export the result of a build or the set of the sources to a project.", groupCommands = { + Export.ExportPortable.class, Export.ExportLocal.class, Export.ExportMavenPublish.class, + Export.ExportNative.class, Export.ExportFatjar.class, Export.ExportJlink.class, + Export.ExportGradleProject.class, Export.ExportMavenProject.class }, generateHelp = true, helpGroup = "Caching", defaultValueProvider = JBangDefaultValueProvider.class) +public class Export extends BaseCommand { @Override public Integer doCall() throws IOException { - exportMixin.validate(); - ProjectBuilder pb = createProjectBuilder(exportMixin); - Project prj = pb.build(exportMixin.scriptMixin.scriptOrFile); - BuildContext ctx = BuildContext.forProject(prj); - Project.codeBuilder(ctx).build(); - if (prj.getResourceRef() instanceof AliasResourceResolver.AliasedResourceRef) { - Alias alias = ((AliasResourceResolver.AliasedResourceRef) prj.getResourceRef()).getAlias(); - if (prj.getMainClass() == null) { - prj.setMainClass(alias.mainClass); - } - } - return apply(ctx); + System.err.println("Missing required subcommand for 'export'"); + return EXIT_INVALID_INPUT; } - abstract int apply(BuildContext ctx) throws IOException; - - protected ProjectBuilder createProjectBuilder(ExportMixin exportMixin) { - return Project - .builder() - .setProperties(exportMixin.dependencyInfoMixin.getProperties()) - .additionalDependencies(exportMixin.dependencyInfoMixin.getDependencies()) - .additionalRepositories(exportMixin.dependencyInfoMixin.getRepositories()) - .additionalClasspaths(exportMixin.dependencyInfoMixin.getClasspaths()) - .additionalSources(exportMixin.scriptMixin.sources) - .additionalResources(exportMixin.scriptMixin.resources) - .forceType(exportMixin.scriptMixin.forceType) - .catalog(exportMixin.scriptMixin.catalog) - .javaVersion(exportMixin.buildMixin.javaVersion) - .mainClass(exportMixin.buildMixin.main) - .moduleName(exportMixin.buildMixin.module) - .compileOptions(exportMixin.buildMixin.compileOptions) - .jdkManager(exportMixin.buildMixin.jdkProvidersMixin.getJdkManager()); - } + static abstract class BaseExportCommand extends BaseBuildCommand { - Path getJarOutputPath() { - Path outputPath = exportMixin.getOutputPath(""); - // Ensure the file ends in `.jar` - if (!outputPath.toString().endsWith(".jar")) { - outputPath = Paths.get(outputPath + ".jar"); - } - return outputPath; - } -} + @Option(shortName = 'O', name = "output", description = "The name or path to use for the exported file.") + String outputFile; -@Command(name = "local", description = "Exports jar with classpath referring to local machine dependent locations") -class ExportLocal extends BaseExportCommand { + @Option(name = "force", hasValue = false, description = "Force export, i.e. overwrite exported file if already exists") + boolean force; - @Override - int apply(BuildContext ctx) throws IOException { - // Copy the JAR - Path source = ctx.getJarFile(); - Path outputPath = getJarOutputPath(); - if (outputPath.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(outputPath, false); + // Create a Manifest with a Class-Path option + protected Manifest createManifest(String newPath) { + Manifest mf = new Manifest(); + // without MANIFEST_VERSION nothing is saved. + mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + mf.getMainAttributes().putValue(Attributes.Name.CLASS_PATH.toString(), newPath); + return mf; + } + + Path getOutputPath(String postFix) { + Path cwd = Util.getCwd(); + Path outPath; + if (outputFile != null) { + outPath = Paths.get(outputFile); } else { - Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); - return EXIT_INVALID_INPUT; + String outName = CatalogUtil.nameFromRef(scriptMixin.scriptOrFile); + outPath = Paths.get(outName + postFix); } - } else { - Util.mkdirs(outputPath.getParent()); + outPath = cwd.resolve(outPath); + return outPath; + } + + @Override + public Integer doCall() throws IOException { + scriptMixin.validate(); + ProjectBuilder pb = createExportProjectBuilder(); + Project prj = pb.build(scriptMixin.scriptOrFile); + BuildContext ctx = BuildContext.forProject(prj); + Project.codeBuilder(ctx).build(); + if (prj.getResourceRef() instanceof AliasResourceResolver.AliasedResourceRef) { + Alias alias = ((AliasResourceResolver.AliasedResourceRef) prj.getResourceRef()).getAlias(); + if (prj.getMainClass() == null) { + prj.setMainClass(alias.mainClass); + } + } + return apply(ctx); } - Files.copy(source, outputPath); - // Update the JAR's MANIFEST.MF Class-Path to point to - // its dependencies - Project prj = ctx.getProject(); - String newPath = ctx.resolveClassPath().getManifestPath(); - if (!newPath.isEmpty()) { - Util.infoMsg("Updating jar..."); - JarUtil.updateJar(outputPath, createManifest(newPath), prj.getMainClass(), - exportMixin.buildMixin.getProjectJdk(prj)); + abstract int apply(BuildContext ctx) throws IOException; + + protected ProjectBuilder createExportProjectBuilder() { + return createBaseProjectBuilder().mainClass(buildMixin.main); } - Util.infoMsg("Exported to " + outputPath); - return EXIT_OK; + Path getJarOutputPath() { + Path outPath = getOutputPath(""); + // Ensure the file ends in `.jar` + if (!outPath.toString().endsWith(".jar")) { + outPath = Paths.get(outPath + ".jar"); + } + return outPath; + } } -} -@Command(name = "portable", description = "Exports jar together with dependencies in way that makes it portable") -class ExportPortable extends BaseExportCommand { + @CommandDefinition(name = "local", description = "Exports jar with classpath referring to local machine dependent locations", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportLocal extends BaseExportCommand { - public static final String LIB = "lib"; + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - @Override - int apply(BuildContext ctx) throws IOException { - // Copy the JAR - Path source = ctx.getJarFile(); - Path outputPath = getJarOutputPath(); - if (outputPath.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(outputPath, false); - } else { - Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); - return EXIT_INVALID_INPUT; - } - } else { - Util.mkdirs(outputPath.getParent()); - } - - Files.copy(source, outputPath); - Project prj = ctx.getProject(); - List deps = ctx.resolveClassPath().getArtifacts(); - if (!deps.isEmpty()) { - // Copy dependencies to "./lib" dir - Path libDir = outputPath.getParent().resolve(LIB); - Util.mkdirs(libDir); - StringBuilder newPath = new StringBuilder(); - for (ArtifactInfo dep : deps) { - if (exportMixin.force) { - Files.copy(dep.getFile(), libDir.resolve(dep.getFile().getFileName()), - StandardCopyOption.REPLACE_EXISTING); + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; + } + + @Override + int apply(BuildContext ctx) throws IOException { + // Copy the JAR + Path source = ctx.getJarFile(); + Path outputPath = getJarOutputPath(); + if (outputPath.toFile().exists()) { + if (force) { + Util.deletePath(outputPath, false); } else { - Files.copy(dep.getFile(), libDir.resolve(dep.getFile().getFileName())); + Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; } - newPath.append(" " + LIB + "/" + dep.getFile().getFileName()); + } else { + Util.mkdirs(outputPath.getParent()); + } + Files.copy(source, outputPath); + + // Update the JAR's MANIFEST.MF Class-Path to point to + // its dependencies + Project prj = ctx.getProject(); + String newPath = ctx.resolveClassPath().getManifestPath(); + if (!newPath.isEmpty()) { + Util.infoMsg("Updating jar..."); + JarUtil.updateJar(outputPath, createManifest(newPath), prj.getMainClass(), + getProjectJdk(prj)); } - Util.infoMsg("Updating jar..."); - JarUtil.updateJar(outputPath, createManifest(newPath.toString()), prj.getMainClass(), - exportMixin.buildMixin.getProjectJdk(prj)); + Util.infoMsg("Exported to " + outputPath); + return EXIT_OK; } - Util.infoMsg("Exported to " + outputPath); - return EXIT_OK; } -} - -@Command(name = "mavenrepo", description = "Exports directory that can be used to publish as a maven repository") -class ExportMavenPublish extends BaseExportCommand { - @CommandLine.Option(names = { "--group", "-g" }, description = "The group ID to use for the generated POM.") - String group; + @CommandDefinition(name = "portable", description = "Exports jar together with dependencies in way that makes it portable", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportPortable extends BaseExportCommand { - @CommandLine.Option(names = { "--artifact", "-a" }, description = "The artifact ID to use for the generated POM.") - String artifact; + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - @CommandLine.Option(names = { "--version", "-v" }, description = "The version to use for the generated POM.") - String version; - - @Override - int apply(BuildContext ctx) throws IOException { - Path outputPath = exportMixin.outputFile; - - if (outputPath == null) { - outputPath = ArtifactResolver.getLocalMavenRepo(); + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; } - // Copy the JAR - Path source = ctx.getJarFile(); - if (!outputPath.toFile().isDirectory()) { + public static final String LIB = "lib"; + + @Override + int apply(BuildContext ctx) throws IOException { + // Copy the JAR + Path source = ctx.getJarFile(); + Path outputPath = getJarOutputPath(); if (outputPath.toFile().exists()) { - Util.errorMsg("Cannot export as maven repository as " + outputPath + " is not a directory."); - return EXIT_INVALID_INPUT; - } - if (exportMixin.force) { - Util.mkdirs(outputPath); + if (force) { + Util.deletePath(outputPath, false); + } else { + Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } } else { - Util.errorMsg("Cannot export as " + outputPath + " does not exist. Use --force to create."); - return EXIT_INVALID_INPUT; + Util.mkdirs(outputPath.getParent()); } - } - Project prj = ctx.getProject(); - if (prj.getGav().isPresent()) { - MavenCoordinate coord = MavenCoordinate.fromString(prj.getGav().get()).withVersion(); - if (group == null) { - group = coord.getGroupId(); - } - if (artifact == null) { - artifact = coord.getArtifactId(); - } - if (version == null) { - version = coord.getVersion(); + Files.copy(source, outputPath); + Project prj = ctx.getProject(); + List deps = ctx.resolveClassPath().getArtifacts(); + if (!deps.isEmpty()) { + // Copy dependencies to "./lib" dir + Path libDir = outputPath.getParent().resolve(LIB); + Util.mkdirs(libDir); + StringBuilder newPath = new StringBuilder(); + for (ArtifactInfo dep : deps) { + if (force) { + Files.copy(dep.getFile(), libDir.resolve(dep.getFile().getFileName()), + StandardCopyOption.REPLACE_EXISTING); + } else { + Files.copy(dep.getFile(), libDir.resolve(dep.getFile().getFileName())); + } + newPath.append(" " + LIB + "/" + dep.getFile().getFileName()); + } + + Util.infoMsg("Updating jar..."); + JarUtil.updateJar(outputPath, createManifest(newPath.toString()), prj.getMainClass(), + getProjectJdk(prj)); } + Util.infoMsg("Exported to " + outputPath); + return EXIT_OK; } + } - if (group == null) { - Util.errorMsg( - "Cannot export as maven repository as no group specified. Add --group= and run again."); - return EXIT_INVALID_INPUT; + @CommandDefinition(name = "mavenrepo", description = "Exports directory that can be used to publish as a maven repository", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportMavenPublish extends BaseExportCommand { + + @Argument(description = "A file or URL to a Java code file") + String scriptArg; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; } - Path groupdir = outputPath.resolve(Paths.get(group.replace(".", "/"))); - artifact = artifact != null ? artifact - : Util.getBaseName(prj.getResourceRef().getFile().getFileName().toString()); - Path artifactDir = groupdir.resolve(artifact); + @Option(shortName = 'g', name = "group", description = "The group ID to use for the generated POM.") + String group; - version = version != null ? version : MavenCoordinate.DEFAULT_VERSION; - Path versionDir = artifactDir.resolve(version); + @Option(shortName = 'a', name = "artifact", description = "The artifact ID to use for the generated POM.") + String artifact; - String suffix = source.getFileName() - .toString() - .substring(source.getFileName().toString().lastIndexOf(".")); - Path artifactFile = versionDir.resolve(artifact + "-" + version + suffix); + @Option(shortName = 'v', name = "version", description = "The version to use for the generated POM.") + String version; - artifactFile.getParent().toFile().mkdirs(); + @Override + int apply(BuildContext ctx) throws IOException { + Path outPath = outputFile != null ? Paths.get(outputFile) : null; - if (artifactFile.toFile().exists()) { - if (exportMixin.force) { - artifactFile.toFile().delete(); - } else { - Util.errorMsg("Cannot export as " + artifactFile + " already exists. Use --force to overwrite."); + if (outPath == null) { + outPath = ArtifactResolver.getLocalMavenRepo(); + } + Path source = ctx.getJarFile(); + + if (!outPath.toFile().isDirectory()) { + if (outPath.toFile().exists()) { + Util.errorMsg("Cannot export as maven repository as " + outPath + " is not a directory."); + return EXIT_INVALID_INPUT; + } + if (force) { + Util.mkdirs(outPath); + } else { + Util.errorMsg("Cannot export as " + outPath + " does not exist. Use --force to create."); + return EXIT_INVALID_INPUT; + } + } + + Project prj = ctx.getProject(); + if (prj.getGav().isPresent()) { + MavenCoordinate coord = MavenCoordinate.fromString(prj.getGav().get()).withVersion(); + if (group == null) { + group = coord.getGroupId(); + } + if (artifact == null) { + artifact = coord.getArtifactId(); + } + if (version == null) { + version = coord.getVersion(); + } + } + + if (group == null) { + Util.errorMsg( + "Cannot export as maven repository as no group specified. Add --group= and run again."); return EXIT_INVALID_INPUT; } - } - Util.infoMsg("Writing " + artifactFile); - Files.copy(source, artifactFile); + Path groupdir = outPath.resolve(Paths.get(group.replace(".", "/"))); - // generate pom.xml ... if jar could technically just copy from the jar ...but - // not possible when native thus for now just regenerate it - Template pomTemplate = TemplateEngine.instance() - .getTemplate(ResourceRef.forResource("classpath:/pom.qute.xml")); + artifact = artifact != null ? artifact + : Util.getBaseName(prj.getResourceRef().getFile().getFileName().toString()); + Path artifactDir = groupdir.resolve(artifact); - Path pomPath = versionDir.resolve(artifact + "-" + version + ".pom"); - if (pomTemplate == null) { - // ignore - Util.warnMsg("Could not locate pom.xml template"); - } else { + version = version != null ? version : MavenCoordinate.DEFAULT_VERSION; + Path versionDir = artifactDir.resolve(version); - String pomfile = pomTemplate - .data("baseName", - Util.getBaseName( - prj.getResourceRef().getFile().getFileName().toString())) - .data("group", group) - .data("artifact", artifact) - .data("version", version) - .data("description", prj.getDescription().orElse("")) - .data("dependencies", ctx.resolveClassPath().getArtifacts()) - .render(); - Util.infoMsg("Writing " + pomPath); - Util.writeString(pomPath, pomfile); + String suffix = source.getFileName() + .toString() + .substring(source.getFileName().toString().lastIndexOf(".")); + Path artifactFile = versionDir.resolve(artifact + "-" + version + suffix); - } + artifactFile.getParent().toFile().mkdirs(); - Util.infoMsg("Exported to " + outputPath); - return EXIT_OK; - } -} + if (artifactFile.toFile().exists()) { + if (force) { + artifactFile.toFile().delete(); + } else { + Util.errorMsg("Cannot export as " + artifactFile + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } + } + Util.infoMsg("Writing " + artifactFile); + Files.copy(source, artifactFile); -@Command(name = "native", description = "Exports native executable") -class ExportNative extends BaseExportCommand { + // generate pom.xml ... if jar could technically just copy from the jar ...but + // not possible when native thus for now just regenerate it + Template pomTemplate = TemplateEngine.instance() + .getTemplate(ResourceRef.forResource("classpath:/pom.qute.xml")); - @Override - int apply(BuildContext ctx) throws IOException { - // Copy the native binary - Path source = ctx.getNativeImageFile(); - Path outputPath = getNativeOutputPath(); - if (outputPath.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(outputPath, false); + Path pomPath = versionDir.resolve(artifact + "-" + version + ".pom"); + if (pomTemplate == null) { + Util.warnMsg("Could not locate pom.xml template"); } else { - Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); - return EXIT_INVALID_INPUT; + String pomfile = pomTemplate + .data("baseName", + Util.getBaseName( + prj.getResourceRef().getFile().getFileName().toString())) + .data("group", group) + .data("artifact", artifact) + .data("version", version) + .data("description", prj.getDescription().orElse("")) + .data("dependencies", ctx.resolveClassPath().getArtifacts()) + .render(); + Util.infoMsg("Writing " + pomPath); + Util.writeString(pomPath, pomfile); } - } else { - Util.mkdirs(outputPath.getParent()); - } - Files.copy(source, outputPath); - Util.infoMsg("Exported to " + outputPath); - return EXIT_OK; + Util.infoMsg("Exported to " + outPath); + return EXIT_OK; + } } - @Override - protected ProjectBuilder createProjectBuilder(ExportMixin exportMixin) { - ProjectBuilder pb = super.createProjectBuilder(exportMixin); - pb.nativeImage(true); - return pb; - } + @CommandDefinition(name = "native", description = "Exports native executable", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportNative extends BaseExportCommand { - Path getNativeOutputPath() { - Path outputPath = exportMixin.getOutputPath(""); - // Ensure that on Windows the file ends in `.exe` - if (Util.isWindows() && !outputPath.toString().endsWith(".exe")) { - outputPath = Paths.get(outputPath + ".exe"); - } - return outputPath; - } -} + @Argument(description = "A file or URL to a Java code file") + String scriptArg; -@Command(name = "fatjar", description = "Exports an executable jar with all necessary dependencies included inside") -class ExportFatjar extends BaseExportCommand { + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; + } - @Override - int apply(BuildContext ctx) throws IOException { - // Copy the native binary - Path source = ctx.getJarFile(); - Path outputPath = getFatjarOutputPath(); - if (outputPath.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(outputPath, false); + @Override + int apply(BuildContext ctx) throws IOException { + // Copy the native binary + Path source = ctx.getNativeImageFile(); + Path outputPath = getNativeOutputPath(); + if (outputPath.toFile().exists()) { + if (force) { + Util.deletePath(outputPath, false); + } else { + Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } } else { - Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); - return EXIT_INVALID_INPUT; + Util.mkdirs(outputPath.getParent()); } - } else { - Util.mkdirs(outputPath.getParent()); + Files.copy(source, outputPath); + + Util.infoMsg("Exported to " + outputPath); + return EXIT_OK; } - Project prj = ctx.getProject(); - List deps = ctx.resolveClassPath().getArtifacts(); - if (!deps.isEmpty()) { - // Extract main jar and all dependencies to a temp dir - Path tmpDir = Files.createTempDirectory("fatjar"); - try { - Util.verboseMsg("Unpacking main jar: " + source); - UnpackUtil.unzip(source, tmpDir, false, null, ExportFatjar::handleZipFile); - for (ArtifactInfo dep : deps) { - Util.verboseMsg("Unpacking artifact: " + dep); - UnpackUtil.unzip(dep.getFile(), tmpDir, false, null, ExportFatjar::handleZipFile); - } - // Remove all signature files - Path metaInf = tmpDir.resolve("META-INF"); - if (Files.exists(metaInf) && Files.isDirectory(metaInf)) { - try (DirectoryStream stream = Files.newDirectoryStream(metaInf, "*.{SF,DSA,RSA}")) { - for (Path entry : stream) { - Util.verboseMsg("Removing signature file: " + entry); - Files.delete(entry); - } - } - } - Util.verboseMsg("Creating jar: " + outputPath); - JarUtil.createJar(outputPath, tmpDir, null, prj.getMainClass(), - exportMixin.buildMixin.getProjectJdk(prj)); - } finally { - Util.deletePath(tmpDir, true); - } - } else { - // No dependencies so we simply copy the main jar - Files.copy(source, outputPath); + @Override + protected ProjectBuilder createExportProjectBuilder() { + ProjectBuilder pb = super.createExportProjectBuilder(); + pb.nativeImage(true); + return pb; } - Util.infoMsg("Exported to " + outputPath); - Util.infoMsg("This is an experimental feature and might not to work for certain applications!"); - Util.infoMsg("Help us improve by reporting any issue you find at https://github.com/jbangdev/jbang/issues"); - return EXIT_OK; + Path getNativeOutputPath() { + Path outputPath = getOutputPath(""); + // Ensure that on Windows the file ends in `.exe` + if (Util.isWindows() && !outputPath.toString().endsWith(".exe")) { + outputPath = Paths.get(outputPath + ".exe"); + } + return outputPath; + } } - public static void handleZipFile(ZipFile zipFile, ZipArchiveEntry zipEntry, Path outFile, Exception ex) - throws IOException { - if (!zipEntry.isDirectory() && outFile.getFileName().toString().equals("module-info.class")) { - Util.verboseMsg("Skipping module-info.class"); - return; - } - if (!Files.exists(outFile) && !(ex instanceof FileAlreadyExistsException)) { - UnpackUtil.defaultZipEntryCopy(zipFile, zipEntry, outFile, ex); - return; + @CommandDefinition(name = "fatjar", description = "Exports an executable jar with all necessary dependencies included inside", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportFatjar extends BaseExportCommand { + + @Argument(description = "A file or URL to a Java code file") + String scriptArg; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; } - if (Files.isDirectory(outFile) != zipEntry.isDirectory()) { - Util.warnMsg("Skipping conflicting duplicate file vs directory: " + zipEntry.getName()); - return; + + @Override + int apply(BuildContext ctx) throws IOException { + Path source = ctx.getJarFile(); + Path outputPath = getFatjarOutputPath(); + if (outputPath.toFile().exists()) { + if (force) { + Util.deletePath(outputPath, false); + } else { + Util.errorMsg("Cannot export as " + outputPath + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } + } else { + Util.mkdirs(outputPath.getParent()); + } + + Project prj = ctx.getProject(); + List deps = ctx.resolveClassPath().getArtifacts(); + if (!deps.isEmpty()) { + // Extract main jar and all dependencies to a temp dir + Path tmpDir = Files.createTempDirectory("fatjar"); + try { + Util.verboseMsg("Unpacking main jar: " + source); + UnpackUtil.unzip(source, tmpDir, false, null, ExportFatjar::handleZipFile); + for (ArtifactInfo dep : deps) { + Util.verboseMsg("Unpacking artifact: " + dep); + UnpackUtil.unzip(dep.getFile(), tmpDir, false, null, ExportFatjar::handleZipFile); + } + // Remove all signature files + Path metaInf = tmpDir.resolve("META-INF"); + if (Files.exists(metaInf) && Files.isDirectory(metaInf)) { + try (DirectoryStream stream = Files.newDirectoryStream(metaInf, "*.{SF,DSA,RSA}")) { + for (Path entry : stream) { + Util.verboseMsg("Removing signature file: " + entry); + Files.delete(entry); + } + } + } + Util.verboseMsg("Creating jar: " + outputPath); + JarUtil.createJar(outputPath, tmpDir, null, prj.getMainClass(), + getProjectJdk(prj)); + } finally { + Util.deletePath(tmpDir, true); + } + } else { + // No dependencies so we simply copy the main jar + Files.copy(source, outputPath); + } + + Util.infoMsg("Exported to " + outputPath); + Util.infoMsg("This is an experimental feature and might not to work for certain applications!"); + Util.infoMsg("Help us improve by reporting any issue you find at https://github.com/jbangdev/jbang/issues"); + return EXIT_OK; } - if (zipEntry.getName().startsWith("META-INF/services/")) { - Util.verboseMsg("Merging service files: " + zipEntry.getName()); - try (ReadableByteChannel readableByteChannel = Channels.newChannel(zipFile.getInputStream(zipEntry)); - FileOutputStream fileOutputStream = new FileOutputStream(outFile.toFile(), true)) { - fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + + public static void handleZipFile(ZipFile zipFile, ZipArchiveEntry zipEntry, Path outFile, Exception ex) + throws IOException { + if (!zipEntry.isDirectory() && outFile.getFileName().toString().equals("module-info.class")) { + Util.verboseMsg("Skipping module-info.class"); + return; + } + if (!Files.exists(outFile) && !(ex instanceof FileAlreadyExistsException)) { + UnpackUtil.defaultZipEntryCopy(zipFile, zipEntry, outFile, ex); + return; + } + if (Files.isDirectory(outFile) != zipEntry.isDirectory()) { + Util.warnMsg("Skipping conflicting duplicate file vs directory: " + zipEntry.getName()); + return; + } + if (zipEntry.getName().startsWith("META-INF/services/")) { + Util.verboseMsg("Merging service files: " + zipEntry.getName()); + try (ReadableByteChannel readableByteChannel = Channels.newChannel(zipFile.getInputStream(zipEntry)); + FileOutputStream fileOutputStream = new FileOutputStream(outFile.toFile(), true)) { + fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); + } + } else { + Util.verboseMsg("Skipping duplicate file: " + zipEntry.getName()); } - } else { - Util.verboseMsg("Skipping duplicate file: " + zipEntry.getName()); } - } - private Path getFatjarOutputPath() { - Path outputPath = exportMixin.getOutputPath("-fatjar"); - // Ensure the file ends in `.jar` - if (!outputPath.toString().endsWith(".jar")) { - outputPath = Paths.get(outputPath + ".jar"); + private Path getFatjarOutputPath() { + Path outputPath = getOutputPath("-fatjar"); + // Ensure the file ends in `.jar` + if (!outputPath.toString().endsWith(".jar")) { + outputPath = Paths.get(outputPath + ".jar"); + } + return outputPath; } - return outputPath; } -} -@Command(name = "jlink", description = "Exports a minimized JDK distribution") -class ExportJlink extends BaseExportCommand { + @CommandDefinition(name = "jlink", description = "Exports a minimized JDK distribution", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportJlink extends BaseExportCommand { - @CommandLine.Parameters(index = "1..*", arity = "0..*", description = "Parameters to pass on to the jlink command") - public List params = new ArrayList<>(); + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - @Override - protected ProjectBuilder createProjectBuilder(ExportMixin exportMixin) { - ProjectBuilder pb = super.createProjectBuilder(exportMixin); - pb.moduleName(""); - return pb; - } + @Arguments(description = "jlink parameters") + public List params; - @Override - int apply(BuildContext ctx) throws IOException { - Project prj = ctx.getProject(); - List artifacts = ctx.resolveClassPath().getArtifacts(); - List nonMods = artifacts - .stream() - .filter(a -> !ModuleUtil.isModule(a.getFile())) - .collect(Collectors.toList()); - if (!nonMods.isEmpty()) { - String lst = nonMods - .stream() - .map(a -> a.getCoordinate().toCanonicalForm()) - .collect(Collectors.joining(", ")); - Util.warnMsg("Export might fail because some dependencies are not full modules: " + lst); + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; } - Path outputPath = getJlinkOutputPath(); - String relativeOP; - if (Util.getCwd().relativize(outputPath).isAbsolute()) { - relativeOP = outputPath.toString(); - } else { - relativeOP = "." + File.separator + Util.getCwd().relativize(outputPath); + @Override + protected ProjectBuilder createExportProjectBuilder() { + ProjectBuilder pb = super.createExportProjectBuilder(); + pb.moduleName(""); + return pb; } - if (outputPath.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(outputPath, false); + + @Override + int apply(BuildContext ctx) throws IOException { + Project prj = ctx.getProject(); + List artifacts = ctx.resolveClassPath().getArtifacts(); + List nonMods = artifacts + .stream() + .filter(a -> !ModuleUtil.isModule(a.getFile())) + .collect(Collectors.toList()); + if (!nonMods.isEmpty()) { + String lst = nonMods + .stream() + .map(a -> a.getCoordinate().toCanonicalForm()) + .collect(Collectors.joining(", ")); + Util.warnMsg("Export might fail because some dependencies are not full modules: " + lst); + } + + Path outputPath = getJlinkOutputPath(); + String relativeOP; + if (Util.getCwd().relativize(outputPath).isAbsolute()) { + relativeOP = outputPath.toString(); } else { - Util.errorMsg("Cannot export as " + relativeOP + " already exists. Use --force to overwrite."); - return EXIT_INVALID_INPUT; + relativeOP = "." + File.separator + Util.getCwd().relativize(outputPath); + } + if (outputPath.toFile().exists()) { + if (force) { + Util.deletePath(outputPath, false); + } else { + Util.errorMsg("Cannot export as " + relativeOP + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } } - } - String jlinkCmd = JavaUtil.resolveInJavaHome("jlink", prj.projectJdk()); - String modMain = ModuleUtil.getModuleMain(prj); - List cps = artifacts.stream().map(a -> a.getFile().toString()).collect(Collectors.toList()); - List cp = new ArrayList<>(artifacts.size() + 1); - if (ctx.getJarFile() != null && !cps.contains(ctx.getJarFile().toString())) { - cp.add(ctx.getJarFile().toString()); - } - cp.addAll(cps); + String jlinkCmd = JavaUtil.resolveInJavaHome("jlink", prj.projectJdk()); + String modMain = ModuleUtil.getModuleMain(prj); + List cps = artifacts.stream().map(a -> a.getFile().toString()).collect(Collectors.toList()); + List cp = new ArrayList<>(artifacts.size() + 1); + if (ctx.getJarFile() != null && !cps.contains(ctx.getJarFile().toString())) { + cp.add(ctx.getJarFile().toString()); + } + cp.addAll(cps); + + List args = new ArrayList<>(); + args.add(jlinkCmd); + args.add("--output"); + args.add(outputPath.toString()); + args.add("-p"); + args.add(String.join(CP_SEPARATOR, cp)); + args.add("--add-modules"); + args.add(ModuleUtil.getModuleName(prj)); + String launcherName = null; + if (modMain != null) { + launcherName = CatalogUtil.nameFromRef(scriptMixin.scriptOrFile); + args.add("--launcher"); + args.add(launcherName + "=" + modMain); + } else { + Util.warnMsg( + "No launcher will be generated because no main class is defined. Use '--main' to set a main class"); + } + if (params != null) { + args.addAll(params); + } - List args = new ArrayList<>(); - args.add(jlinkCmd); - args.add("--output"); - args.add(outputPath.toString()); - args.add("-p"); - args.add(String.join(CP_SEPARATOR, cp)); - args.add("--add-modules"); - args.add(ModuleUtil.getModuleName(prj)); - String launcherName = null; - if (modMain != null) { - launcherName = CatalogUtil.nameFromRef(exportMixin.scriptMixin.scriptOrFile); - args.add("--launcher"); - args.add(launcherName + "=" + modMain); - } else { - Util.warnMsg( - "No launcher will be generated because no main class is defined. Use '--main' to set a main class"); - } - args.addAll(params); + Util.verboseMsg("Run: " + String.join(" ", args)); + String out = Util.runCommand(args.toArray(new String[] {})); + if (out == null) { + Util.errorMsg("Unable to export Jdk distribution."); + return EXIT_GENERIC_ERROR; + } - Util.verboseMsg("Run: " + String.join(" ", args)); - String out = Util.runCommand(args.toArray(new String[] {})); - if (out == null) { - Util.errorMsg("Unable to export Jdk distribution."); - return EXIT_GENERIC_ERROR; + Util.infoMsg("Exported to " + relativeOP); + if (modMain != null) { + Util.infoMsg( + "A launcher has been created which you can run using: " + relativeOP + "/bin/" + launcherName); + } + return EXIT_OK; } - Util.infoMsg("Exported to " + relativeOP); - if (modMain != null) { - Util.infoMsg("A launcher has been created which you can run using: " + relativeOP + "/bin/" + launcherName); + private Path getJlinkOutputPath() { + return getOutputPath("-jlink"); } - return EXIT_OK; } - private Path getJlinkOutputPath() { - Path outputPath = exportMixin.getOutputPath("-jlink"); - return outputPath; - } -} + static abstract class BaseExportProject extends BaseExportCommand { -abstract class BaseExportProject extends BaseExportCommand { + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - @CommandLine.Option(names = { "--group", "-g" }, description = "The group ID to use for the exported project.") - String group; + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) + scriptMixin.scriptOrFile = scriptArg; + } - @CommandLine.Option(names = { "--artifact", - "-a" }, description = "The artifact ID to use for the exported project.") - String artifact; + @Option(shortName = 'g', name = "group", description = "The group ID to use for the exported project.") + String group; - @CommandLine.Option(names = { "--version", "-v" }, description = "The version to use for the exported project.") - String version; + @Option(shortName = 'a', name = "artifact", description = "The artifact ID to use for the exported project.") + String artifact; - protected EnumSet supportedSourceTypes = EnumSet.of(Source.Type.java, Source.Type.groovy, - Source.Type.kotlin); + @Option(shortName = 'v', name = "version", description = "The version to use for the exported project.") + String version; - @Override - int apply(BuildContext ctx) throws IOException { - Path projectDir = exportMixin.getOutputPath(""); - if (projectDir.toFile().exists()) { - if (exportMixin.force) { - Util.deletePath(projectDir, false); - } else { - Util.errorMsg("Cannot export as " + projectDir + " already exists. Use --force to overwrite."); + protected EnumSet supportedSourceTypes = EnumSet.of(Source.Type.java, Source.Type.groovy, + Source.Type.kotlin); + + @Override + int apply(BuildContext ctx) throws IOException { + Path projectDir = getOutputPath(""); + if (projectDir.toFile().exists()) { + if (force) { + Util.deletePath(projectDir, false); + } else { + Util.errorMsg("Cannot export as " + projectDir + " already exists. Use --force to overwrite."); + return EXIT_INVALID_INPUT; + } + } + + Project prj = ctx.getProject(); + if (prj.isExecutableArchive() || prj.getMainSourceSet().getSources().isEmpty()) { + Util.errorMsg("You can only export source files"); return EXIT_INVALID_INPUT; } - } - Project prj = ctx.getProject(); - if (prj.isExecutableArchive() || prj.getMainSourceSet().getSources().isEmpty()) { - Util.errorMsg("You can only export source files"); - return EXIT_INVALID_INPUT; - } + if (prj.getGav().isPresent()) { + MavenCoordinate coord = MavenCoordinate.fromString(prj.getGav().get()).withVersion(); + if (group == null) { + group = coord.getGroupId(); + } + if (artifact == null) { + artifact = coord.getArtifactId(); + } + if (version == null) { + version = coord.getVersion(); + } + } - if (prj.getGav().isPresent()) { - MavenCoordinate coord = MavenCoordinate.fromString(prj.getGav().get()).withVersion(); if (group == null) { - group = coord.getGroupId(); - } - if (artifact == null) { - artifact = coord.getArtifactId(); + group = "org.example.project"; + info("No explicit group found, using " + group + " as fallback."); } - if (version == null) { - version = coord.getVersion(); - } - } + artifact = artifact != null ? artifact + : Util.getBaseName(Objects.requireNonNull(prj.getResourceRef().getFile()).getFileName().toString()); + version = version != null ? version : MavenCoordinate.DEFAULT_VERSION; + + createProjectForExport(ctx, projectDir); - if (group == null) { - group = "org.example.project"; - info("No explicit group found, using " + group + " as fallback."); + info("Exported as " + getType() + " project to " + projectDir); + info("Export to " + getType() + " is a best-effort to try match JBang build."); + info("If something can be improved please open issue at open an issue at https://github.com/jbangdev/jbang/issues with reproducer."); + return EXIT_OK; } - artifact = artifact != null ? artifact - : Util.getBaseName(Objects.requireNonNull(prj.getResourceRef().getFile()).getFileName().toString()); - version = version != null ? version : MavenCoordinate.DEFAULT_VERSION; - createProjectForExport(ctx, projectDir); + private void createProjectForExport(BuildContext ctx, Path projectDir) throws IOException { + Project prj = ctx.getProject(); + Util.mkdirs(projectDir); - info("Exported as " + getType() + " project to " + projectDir); - info("Export to " + getType() + " is a best-effort to try match JBang build."); - info("If something can be improved please open issue at open an issue at https://github.com/jbangdev/jbang/issues with reproducer."); - return EXIT_OK; - } + String srcFolder = ctx.getProject().getMainSource().getType().sourceFolder; + Path srcJavaDir = projectDir.resolve("src/main/" + srcFolder); + for (ResourceRef sourceRef : prj.getMainSourceSet().getSources()) { + copySource(sourceRef, srcJavaDir); + } - private void createProjectForExport(BuildContext ctx, Path projectDir) throws IOException { - Project prj = ctx.getProject(); - Util.mkdirs(projectDir); + Path srcResourcesDir = projectDir.resolve("src/main/resources"); + for (RefTarget ref : prj.getMainSourceSet().getResources()) { + Path destFile = ref.to(srcResourcesDir); + Util.mkdirs(destFile.getParent()); + Files.copy(Objects.requireNonNull(ref.getSource().getFile()).toAbsolutePath(), destFile); + } - // Sources - String srcFolder = ctx.getProject().getMainSource().getType().sourceFolder; - Path srcJavaDir = projectDir.resolve("src/main/" + srcFolder); - for (ResourceRef sourceRef : prj.getMainSourceSet().getSources()) { - copySource(sourceRef, srcJavaDir); + renderBuildFile(ctx, projectDir); + installWrapperFiles(ctx, projectDir); } - // Resources - Path srcResourcesDir = projectDir.resolve("src/main/resources"); - for (RefTarget ref : prj.getMainSourceSet().getResources()) { - Path destFile = ref.to(srcResourcesDir); - Util.mkdirs(destFile.getParent()); - Files.copy(Objects.requireNonNull(ref.getSource().getFile()).toAbsolutePath(), destFile); + private Path copySource(ResourceRef sourceRef, Path srcJavaDir) throws IOException { + Path srcFile = Objects.requireNonNull(sourceRef.getFile()); + Source src = Source.forResourceRef(sourceRef, Function.identity()); + String fileName = Util.unkebabify(srcFile.getFileName().toString()); + Path destFile; + if (src.getJavaPackage().isPresent()) { + Path packageDir = srcJavaDir.resolve(src.getJavaPackage().get().replace(".", File.separator)); + Util.mkdirs(packageDir); + destFile = packageDir.resolve(fileName); + Files.copy(srcFile, destFile); + } else { + destFile = srcJavaDir.resolve(fileName); + Files.createDirectories(destFile.getParent()); + Files.copy(srcFile, destFile); + } + return destFile; } - // Build file - renderBuildFile(ctx, projectDir); + abstract String getType(); - // Wrapper files - installWrapperFiles(ctx, projectDir); - } + abstract void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException; - private Path copySource(ResourceRef sourceRef, Path srcJavaDir) - throws IOException { - Path srcFile = Objects.requireNonNull(sourceRef.getFile()); - Source src = Source.forResourceRef(sourceRef, Function.identity()); - String fileName = Util.unkebabify(srcFile.getFileName().toString()); - Path destFile; - if (src.getJavaPackage().isPresent()) { - Path packageDir = srcJavaDir.resolve(src.getJavaPackage().get().replace(".", File.separator)); - Util.mkdirs(packageDir); - destFile = packageDir.resolve(fileName); - Files.copy(srcFile, destFile); - } else { - destFile = srcJavaDir.resolve(fileName); - Files.createDirectories(destFile.getParent()); - Files.copy(srcFile, destFile); - } - return destFile; - } + abstract void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException; - abstract String getType(); + String getJavaVersion(Project prj, boolean minorVersionFor8) { + if (prj.getJavaVersion() == null) { + return null; + } + int javaVersion = JavaUtil.minRequestedVersion(prj.getJavaVersion()); + if (!minorVersionFor8) { + return Integer.toString(javaVersion); + } + return javaVersion >= 9 ? Integer.toString(javaVersion) : "1." + javaVersion; + } - abstract void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException; + void copyWrapperFile(String srcPath, String dstPath, boolean execFlag) throws IOException { + File dstFile = new File(dstPath); + File dstDir = new File(dstFile.getParent()); - abstract void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException; + dstDir.mkdirs(); + InputStream ifs = this.getClass().getResourceAsStream(srcPath); + Files.copy(ifs, Paths.get(dstPath), StandardCopyOption.REPLACE_EXISTING); - String getJavaVersion(Project prj, boolean minorVersionFor8) { - if (prj.getJavaVersion() == null) { - return null; - } - int javaVersion = JavaUtil.minRequestedVersion(prj.getJavaVersion()); - if (!minorVersionFor8) { - return Integer.toString(javaVersion); + if (Util.isWindows()) + return; + + if (execFlag) { + Util.setExecutable(dstFile.toPath()); + } } - return javaVersion >= 9 ? Integer.toString(javaVersion) : "1." + javaVersion; } - void copyWrapperFile(String srcPath, String dstPath, boolean execFlag) throws IOException { - File dstFile = new File(dstPath); - File dstDir = new File(dstFile.getParent()); + @CommandDefinition(name = "gradle", description = "Exports a Gradle project", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportGradleProject extends BaseExportProject { - dstDir.mkdirs(); - InputStream ifs = this.getClass().getResourceAsStream(srcPath); - Files.copy(ifs, Paths.get(dstPath), StandardCopyOption.REPLACE_EXISTING); + @Override + String getType() { + return "gradle"; + } - if (Util.isWindows()) - return; + @Override + void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException { + Project prj = ctx.getProject(); + ResourceRef templateRef = ResourceRef.forResource("classpath:/export-build.qute.gradle"); + Path destination = projectDir.resolve("build.gradle"); - if (execFlag) { - Util.setExecutable(dstFile.toPath()); + List repositories = prj.getRepositories(); + List dependencies = prj.getMainSourceSet().getDependencies(); + List depIds = dependencies.stream() + .map(JitPackUtil::ensureGAV) + .collect(Collectors.toList()); + if (!depIds.equals(dependencies) + && repositories.stream().noneMatch(r -> DependencyUtil.REPO_JITPACK.equals(r.getUrl()))) { + prj.addRepository(DependencyUtil.toMavenRepo(DependencyUtil.ALIAS_JITPACK)); + } + + TemplateEngine engine = TemplateEngine.instance(); + Template template = engine.getTemplate(templateRef); + if (template == null) + throw new ExitException(EXIT_INVALID_INPUT, "Could not locate template named: '" + templateRef + "'"); + Source.Type srcType = prj.getMainSource().getType(); + if (!supportedSourceTypes.contains(srcType)) { + throw new ExitException(EXIT_INVALID_INPUT, "Unsupported source type: " + srcType.name()); + } + String kotlinVersion = srcType == Source.Type.kotlin + ? ((KotlinSource) prj.getMainSource()).getKotlinVersion() + : ""; + String javaVersion = getJavaVersion(prj, false); + String jvmArgs = gradleArgs(prj.getRuntimeOptions()); + String compilerArgs = gradleArgs(prj.getMainSourceSet().getCompileOptions()); + String result = template + .data("group", group) + .data("artifact", artifact) + .data("version", version) + .data("language", srcType.name()) + .data("javaVersion", javaVersion) + .data("kotlinVersion", kotlinVersion) + .data("description", prj.getDescription().orElse("")) + .data("repositories", repositories.stream() + .map(MavenRepo::getUrl) + .filter(s -> !"".equals(s)) + .collect(Collectors.toList())) + .data("gradledependencies", gradleify(depIds)) + .data("fullClassName", prj.getMainClass()) + .data("jvmArgs", jvmArgs) + .data("enablePreview", prj.enablePreview() ? (javaVersion != null ? "true" : "") : "") + .data("compilerArgs", compilerArgs) + .render(); + Util.writeString(destination, result); + Util.writeString(projectDir.resolve("settings.gradle"), ""); + Util.writeString(projectDir.resolve("gradle.properties"), "org.gradle.configuration-cache=true\n"); + } + + @Override + void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException { + String dir = projectDir.getFileName().toString(); + copyWrapperFile("/dist/gradle/gradlew", Paths.get(dir, "gradlew").toString(), true); + copyWrapperFile("/dist/gradle/gradlew.bat", Paths.get(dir, "gradlew.bat").toString(), false); + copyWrapperFile("/dist/gradle/gradle/wrapper/gradle-wrapper.properties", + Paths.get(dir, "gradle", "wrapper", "gradle-wrapper.properties").toString(), false); + copyWrapperFile("/dist/gradle/gradle/wrapper/gradle-wrapper-jar", + Paths.get(dir, "gradle", "wrapper", "gradle-wrapper.jar").toString(), false); + } + + private List gradleify(List collectDependencies) { + return collectDependencies.stream().map(item -> { + if (item.endsWith("@pom")) { + return "implementation platform ('" + item.substring(0, item.lastIndexOf("@pom")) + "')"; + } else { + return "implementation '" + item + "'"; + } + }).collect(Collectors.toList()); } + private String gradleArgs(List options) { + StringBuilder args = new StringBuilder(); + for (String arg : options) { + if (args.length() > 0) { + args.append(", "); + } + args.append(String.format("'%s'", arg)); + } + return args.toString(); + } } -} + @CommandDefinition(name = "maven", description = "Exports a Maven project", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ExportMavenProject extends BaseExportProject { -@Command(name = "gradle", description = "Exports a Gradle project") -class ExportGradleProject extends BaseExportProject { + @Override + String getType() { + return "maven"; + } - @Override - String getType() { - return "gradle"; - } + @Override + void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException { + Project prj = ctx.getProject(); + ResourceRef templateRef = ResourceRef.forResource("classpath:/export-pom.qute.xml"); + Path destination = projectDir.resolve("pom.xml"); - @Override - void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException { - Project prj = ctx.getProject(); - ResourceRef templateRef = ResourceRef.forResource("classpath:/export-build.qute.gradle"); - Path destination = projectDir.resolve("build.gradle"); - - List repositories = prj.getRepositories(); - List dependencies = prj.getMainSourceSet().getDependencies(); - // Turn any URL dependencies into regular GAV coordinates - List depIds = dependencies.stream() - .map(JitPackUtil::ensureGAV) - .collect(Collectors.toList()); - // And if we encountered URLs let's make sure the JitPack repo is available - if (!depIds.equals(dependencies) - && repositories.stream().noneMatch(r -> DependencyUtil.REPO_JITPACK.equals(r.getUrl()))) { - prj.addRepository(DependencyUtil.toMavenRepo(DependencyUtil.ALIAS_JITPACK)); - } - - TemplateEngine engine = TemplateEngine.instance(); - Template template = engine.getTemplate(templateRef); - if (template == null) - throw new ExitException(EXIT_INVALID_INPUT, "Could not locate template named: '" + templateRef + "'"); - Source.Type srcType = prj.getMainSource().getType(); - if (!supportedSourceTypes.contains(srcType)) { - throw new ExitException(EXIT_INVALID_INPUT, "Unsupported source type: " + srcType.name()); - } - String kotlinVersion = srcType == Source.Type.kotlin ? ((KotlinSource) prj.getMainSource()).getKotlinVersion() - : ""; - String javaVersion = getJavaVersion(prj, false); - String jvmArgs = gradleArgs(prj.getRuntimeOptions()); - String compilerArgs = gradleArgs(prj.getMainSourceSet().getCompileOptions()); - String result = template - .data("group", group) - .data("artifact", artifact) - .data("version", version) - .data("language", srcType.name()) - .data("javaVersion", javaVersion) - .data("kotlinVersion", kotlinVersion) - .data("description", prj.getDescription().orElse("")) - .data("repositories", repositories.stream() - .map(MavenRepo::getUrl) - .filter(s -> !"".equals(s)) - .collect(Collectors.toList())) - .data("gradledependencies", gradleify(depIds)) - .data("fullClassName", prj.getMainClass()) - .data("jvmArgs", jvmArgs) - .data("enablePreview", prj.enablePreview() ? (javaVersion != null ? "true" : "") : "") - .data("compilerArgs", compilerArgs) - .render(); - Util.writeString(destination, result); - Util.writeString(projectDir.resolve("settings.gradle"), ""); - Util.writeString(projectDir.resolve("gradle.properties"), "org.gradle.configuration-cache=true\n"); - } + List repositories = prj.getRepositories(); + List dependencies = prj.getMainSourceSet().getDependencies(); + List depIds = dependencies.stream() + .map(JitPackUtil::ensureGAV) + .collect(Collectors.toList()); + if (!depIds.equals(dependencies) + && repositories.stream().noneMatch(r -> DependencyUtil.REPO_JITPACK.equals(r.getUrl()))) { + prj.addRepository(DependencyUtil.toMavenRepo(DependencyUtil.ALIAS_JITPACK)); + } - @Override - void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException { - String dir = projectDir.getFileName().toString(); - copyWrapperFile("/dist/gradle/gradlew", Paths.get(dir, "gradlew").toString(), true); - copyWrapperFile("/dist/gradle/gradlew.bat", Paths.get(dir, "gradlew.bat").toString(), false); - copyWrapperFile("/dist/gradle/gradle/wrapper/gradle-wrapper.properties", - Paths.get(dir, "gradle", "wrapper", "gradle-wrapper.properties").toString(), false); - copyWrapperFile("/dist/gradle/gradle/wrapper/gradle-wrapper-jar", - Paths.get(dir, "gradle", "wrapper", "gradle-wrapper.jar").toString(), false); - } + List boms = depIds.stream().filter(d -> d.endsWith("@pom")).collect(Collectors.toList()); + depIds = depIds.stream().filter(d -> !d.endsWith("@pom")).collect(Collectors.toList()); - private List gradleify(List collectDependencies) { - return collectDependencies.stream().map(item -> { - if (item.endsWith("@pom")) { - return "implementation platform ('" + item.substring(0, item.lastIndexOf("@pom")) + "')"; - } else { - return "implementation '" + item + "'"; + TemplateEngine engine = TemplateEngine.instance(); + Template template = engine.getTemplate(templateRef); + if (template == null) + throw new ExitException(EXIT_INVALID_INPUT, "Could not locate template named: '" + templateRef + "'"); + Source.Type srcType = prj.getMainSource().getType(); + if (!supportedSourceTypes.contains(srcType)) { + throw new ExitException(EXIT_INVALID_INPUT, "Unsupported source type: " + srcType.name()); } - }).collect(Collectors.toList()); - } - - private String gradleArgs(List options) { - StringBuilder args = new StringBuilder(); - for (String arg : options) { - if (args.length() > 0) { - args.append(", "); + String kotlinVersion = srcType == Source.Type.kotlin + ? ((KotlinSource) prj.getMainSource()).getKotlinVersion() + : ""; + Map properties = new HashMap<>(); + if (srcType == Source.Type.kotlin) { + properties.put("kotlin.version", kotlinVersion); } - args.append(String.format("'%s'", arg)); + String javaVersion = getJavaVersion(prj, false); + String result = template + .data("group", group) + .data("artifact", artifact) + .data("version", version) + .data("language", srcType.name()) + .data("javaVersion", javaVersion) + .data("kotlinVersion", kotlinVersion) + .data("description", prj.getDescription().orElse("")) + .data("properties", properties) + .data("repositories", repositories.stream() + .filter(r -> !r.getUrl().isEmpty()) + .collect(Collectors.toList())) + .data("boms", boms.stream() + .map(MavenCoordinate::fromString) + .collect(Collectors.toList())) + .data("dependencies", depIds.stream() + .map(MavenCoordinate::fromString) + .collect(Collectors.toList())) + .data("fullClassName", prj.getMainClass()) + .data("compilerArgs", prj.getMainSourceSet().getCompileOptions()) + .render(); + Util.writeString(destination, result); } - return args.toString(); - } -} - -@Command(name = "maven", description = "Exports a Maven project") -class ExportMavenProject extends BaseExportProject { - - @Override - String getType() { - return "maven"; - } - @Override - void renderBuildFile(BuildContext ctx, Path projectDir) throws IOException { - Project prj = ctx.getProject(); - ResourceRef templateRef = ResourceRef.forResource("classpath:/export-pom.qute.xml"); - Path destination = projectDir.resolve("pom.xml"); - - List repositories = prj.getRepositories(); - List dependencies = prj.getMainSourceSet().getDependencies(); - // Turn any URL dependencies into regular GAV coordinates - List depIds = dependencies.stream() - .map(JitPackUtil::ensureGAV) - .collect(Collectors.toList()); - // And if we encountered URLs let's make sure the JitPack repo is available - if (!depIds.equals(dependencies) - && repositories.stream().noneMatch(r -> DependencyUtil.REPO_JITPACK.equals(r.getUrl()))) { - prj.addRepository(DependencyUtil.toMavenRepo(DependencyUtil.ALIAS_JITPACK)); - } - - List boms = depIds.stream().filter(d -> d.endsWith("@pom")).collect(Collectors.toList()); - depIds = depIds.stream().filter(d -> !d.endsWith("@pom")).collect(Collectors.toList()); - - TemplateEngine engine = TemplateEngine.instance(); - Template template = engine.getTemplate(templateRef); - if (template == null) - throw new ExitException(EXIT_INVALID_INPUT, "Could not locate template named: '" + templateRef + "'"); - Source.Type srcType = prj.getMainSource().getType(); - if (!supportedSourceTypes.contains(srcType)) { - throw new ExitException(EXIT_INVALID_INPUT, "Unsupported source type: " + srcType.name()); - } - String kotlinVersion = srcType == Source.Type.kotlin ? ((KotlinSource) prj.getMainSource()).getKotlinVersion() - : ""; - Map properties = new HashMap<>(); - if (srcType == Source.Type.kotlin) { - properties.put("kotlin.version", kotlinVersion); - } - String javaVersion = getJavaVersion(prj, false); - String result = template - .data("group", group) - .data("artifact", artifact) - .data("version", version) - .data("language", srcType.name()) - .data("javaVersion", javaVersion) - .data("kotlinVersion", kotlinVersion) - .data("description", prj.getDescription().orElse("")) - .data("properties", properties) - .data("repositories", repositories.stream() - .filter(r -> !r.getUrl().isEmpty()) - .collect(Collectors.toList())) - .data("boms", boms.stream() - .map(MavenCoordinate::fromString) - .collect(Collectors.toList())) - .data("dependencies", depIds.stream() - .map(MavenCoordinate::fromString) - .collect(Collectors.toList())) - .data("fullClassName", prj.getMainClass()) - .data("compilerArgs", prj.getMainSourceSet().getCompileOptions()) - .render(); - Util.writeString(destination, result); - } - - @Override - void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException { - String dir = projectDir.getFileName().toString(); - copyWrapperFile("/dist/maven/mvnw", Paths.get(dir, "mvnw").toString(), true); - copyWrapperFile("/dist/maven/mvnw.cmd", Paths.get(dir, "mvnw.cmd").toString(), false); - copyWrapperFile("/dist/maven/.mvn/wrapper/maven-wrapper.properties", - Paths.get(dir, ".mvn", "wrapper", "maven-wrapper.properties").toString(), false); + @Override + void installWrapperFiles(BuildContext ctx, Path projectDir) throws IOException { + String dir = projectDir.getFileName().toString(); + copyWrapperFile("/dist/maven/mvnw", Paths.get(dir, "mvnw").toString(), true); + copyWrapperFile("/dist/maven/mvnw.cmd", Paths.get(dir, "mvnw.cmd").toString(), false); + copyWrapperFile("/dist/maven/.mvn/wrapper/maven-wrapper.properties", + Paths.get(dir, ".mvn", "wrapper", "maven-wrapper.properties").toString(), false); + } } - -} \ No newline at end of file +} diff --git a/src/main/java/dev/jbang/cli/ExportMixin.java b/src/main/java/dev/jbang/cli/ExportMixin.java deleted file mode 100644 index 6145d6f15..000000000 --- a/src/main/java/dev/jbang/cli/ExportMixin.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.jbang.cli; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import dev.jbang.catalog.CatalogUtil; -import dev.jbang.util.Util; - -import picocli.CommandLine; - -public class ExportMixin { - - @CommandLine.Mixin - ScriptMixin scriptMixin; - - @CommandLine.Mixin - BuildMixin buildMixin; - - @CommandLine.Mixin - DependencyInfoMixin dependencyInfoMixin; - - @CommandLine.Option(names = { "-O", - "--output" }, description = "The name or path to use for the exported file. If not specified a name will be determined from the original source reference and export flags.") - Path outputFile;// mixins todo above - - @CommandLine.Option(names = { - "--force" }, description = "Force export, i.e. overwrite exported file if already exists") - boolean force; - - @Deprecated - @CommandLine.Option(names = { - "-n", "--native" }, description = "Deprecated: use `jbang export native`", hidden = true) - boolean nativeImage; - - public ExportMixin() { - } - - Path getOutputPath(String postFix) { - // Determine the output file location and name - Path cwd = Util.getCwd(); - Path outputPath; - if (outputFile != null) { - outputPath = outputFile; - } else { - String outName = CatalogUtil.nameFromRef(scriptMixin.scriptOrFile); - outputPath = Paths.get(outName + postFix); - } - outputPath = cwd.resolve(outputPath); - return outputPath; - } - - public void validate() { - scriptMixin.validate(); - if (nativeImage) { - throw new IllegalArgumentException( - "The `-n` and `--native` flags have been removed, use `jbang export native` instead."); - } - } -} \ No newline at end of file diff --git a/src/main/java/dev/jbang/cli/ExternalCommandsProvider.java b/src/main/java/dev/jbang/cli/ExternalCommandsProvider.java new file mode 100644 index 000000000..0f17b5b4a --- /dev/null +++ b/src/main/java/dev/jbang/cli/ExternalCommandsProvider.java @@ -0,0 +1,87 @@ +package dev.jbang.cli; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.aesh.command.HelpEntry; +import org.aesh.command.HelpSectionProvider; + +import dev.jbang.catalog.Catalog; +import dev.jbang.util.Util; + +public class ExternalCommandsProvider implements HelpSectionProvider { + + @Override + public Map> getAdditionalSections() { + Map commands = findExternalCommands(); + if (commands.isEmpty()) { + return Collections.emptyMap(); + } + List entries = commands.entrySet() + .stream() + .map(e -> new HelpEntry(e.getKey(), e.getValue())) + .collect(Collectors.toList()); + Map> sections = new LinkedHashMap<>(); + sections.put("External", entries); + return sections; + } + + private static Map findExternalCommands() { + Map result = new TreeMap<>(); + try { + Catalog cat = Catalog.getMerged(true, false); + for (String name : cat.aliases.keySet()) { + if (name.startsWith("jbang-")) { + result.put(name.substring(6), cat.aliases.get(name).description); + } + } + } catch (Exception ex) { + Util.verboseMsg("Error trying to list aliases", ex); + } + try { + List paths = getPluginPaths(); + List cmds = findCommandsWith(paths, p -> p.getFileName().toString().startsWith("jbang-")); + for (Path p : cmds) { + String name = Util.base(p.getFileName().toString()).substring(6); + result.putIfAbsent(name, null); + } + } catch (Exception ex) { + Util.verboseMsg("Error trying to list jbang-commands", ex); + } + return result; + } + + private static List getPluginPaths() { + return Arrays.stream(System.getenv().getOrDefault("PATH", "").split(File.pathSeparator)) + .filter(Util::isValidPath) + .map(Paths::get) + .filter(Files::isDirectory) + .collect(Collectors.toList()); + } + + private static List findCommandsWith(List pathElems, java.util.function.Predicate accept) { + return pathElems.stream() + .filter(Files::isDirectory) + .flatMap(dir -> listFiles(dir).filter(Util::isExecutable).filter(accept)) + .collect(Collectors.toList()); + } + + private static Stream listFiles(Path dir) { + try { + return Files.list(dir); + } catch (IOException e) { + return Stream.empty(); + } + } +} diff --git a/src/main/java/dev/jbang/cli/FormatMixin.java b/src/main/java/dev/jbang/cli/FormatMixin.java deleted file mode 100644 index 69e3b66f8..000000000 --- a/src/main/java/dev/jbang/cli/FormatMixin.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.jbang.cli; - -import picocli.CommandLine; - -public class FormatMixin { - - public enum Format { - text, json - } - - @CommandLine.Option(names = { - "--format" }, description = "Specify output format ('text' or 'json')") - protected Format format; -} diff --git a/src/main/java/dev/jbang/cli/HelpMixin.java b/src/main/java/dev/jbang/cli/HelpMixin.java deleted file mode 100644 index cff29c748..000000000 --- a/src/main/java/dev/jbang/cli/HelpMixin.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.jbang.cli; - -import picocli.CommandLine; - -public class HelpMixin { - - @CommandLine.Option(names = { "-h", - "--help" }, usageHelp = true, description = "Display help/info. Use 'jbang -h' for detailed usage.") - boolean helpRequested; -} diff --git a/src/main/java/dev/jbang/cli/Info.java b/src/main/java/dev/jbang/cli/Info.java index 521969d13..ce94115d2 100644 --- a/src/main/java/dev/jbang/cli/Info.java +++ b/src/main/java/dev/jbang/cli/Info.java @@ -21,6 +21,12 @@ import java.util.Set; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -40,385 +46,395 @@ import dev.jbang.util.ModuleUtil; import dev.jbang.util.Util; -import picocli.CommandLine; -import picocli.CommandLine.Option; - -@CommandLine.Command(name = "info", description = "Provides info about the script for tools (and humans who are tools).", subcommands = { - Tools.class, ClassPath.class, Jar.class, Docs.class }) -public class Info { - @CommandLine.Mixin - HelpMixin helpMixin; -} +@GroupCommandDefinition(name = "info", description = "Provides info about the script for tools (and humans who are tools).", groupCommands = { + Info.Tools.class, Info.ClassPath.class, Info.Jar.class, Info.Docs.class }, generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) +public class Info extends BaseCommand { -abstract class BaseInfoCommand extends BaseCommand { + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'info'"); + return EXIT_INVALID_INPUT; + } - @CommandLine.Mixin - ScriptMixin scriptMixin; + static abstract class BaseInfoCommand extends BaseCommand { - @CommandLine.Mixin - DependencyInfoMixin dependencyInfoMixin; + @Mixin + ScriptMixin scriptMixin; - @CommandLine.Option(names = { - "--build-dir" }, description = "Use given directory for build results") - Path buildDir; + @Mixin + DependencyInfoMixin dependencyInfoMixin; - @CommandLine.Option(names = { - "--module" }, arity = "0..1", description = "Treat resource as a module. Optionally with the given module name", preprocessor = StrictParameterPreprocessor.class) - String module; + @Argument(description = "A file or URL to a Java code file") + String scriptArg; - static class ProjectFile { - String originalResource; - String backingResource; - String target; - String error; + @Option(name = "build-dir", description = "Use given directory for build results") + String buildDir; - ProjectFile(ResourceRef ref) { - originalResource = ref.getOriginalResource(); - backingResource = ref.exists() ? ref.getFile().toString() : null; - error = ref instanceof ResourceRef.UnresolvableResourceRef - ? ((ResourceRef.UnresolvableResourceRef) ref).getReason() - : null; - } + @Option(name = "module", description = "Treat resource as a module. Optionally with the given module name") + String module; - ProjectFile(RefTarget ref) { - this(ref.getSource()); - target = Objects.toString(ref.getTarget(), null); + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; + } + dependencyInfoMixin.applyIgnoreTransitiveRepositories(); } - } - static class Repo { - String id; - String url; + static class ProjectFile { + String originalResource; + String backingResource; + String target; + String error; + + ProjectFile(ResourceRef ref) { + originalResource = ref.getOriginalResource(); + backingResource = ref.exists() ? ref.getFile().toString() : null; + error = ref instanceof ResourceRef.UnresolvableResourceRef + ? ((ResourceRef.UnresolvableResourceRef) ref).getReason() + : null; + } - Repo(MavenRepo repo) { - id = repo.getId(); - url = repo.getUrl(); + ProjectFile(RefTarget ref) { + this(ref.getSource()); + target = Objects.toString(ref.getTarget(), null); + } } - } - static class ScriptInfo { - String originalResource; - String backingResource; - String applicationJar; - String applicationJsa; - String nativeImage; - String mainClass; - List dependencies; - List repositories; - List resolvedDependencies; - String javaVersion; - String requestedJavaVersion; - String availableJdkPath; - List compileOptions; - List runtimeOptions; - List files; - List sources; - String description; - String gav; - String module; - Map> docs; - - public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled) { - originalResource = prj.getResourceRef().getOriginalResource(); + static class Repo { + String id; + String url; - if (scripts.add(originalResource)) { - backingResource = prj.getResourceRef().getFile().toString(); - - init(prj); + Repo(MavenRepo repo) { + id = repo.getId(); + url = repo.getUrl(); + } + } - try { - BuildContext ctx = BuildContext.forProject(prj, buildDir); - init(ctx); - } catch (Exception e) { - Util.warnMsg("Unable to obtain full information, the script probably contains errors", e); - } + static class ScriptInfo { + String originalResource; + String backingResource; + String applicationJar; + String applicationJsa; + String nativeImage; + String mainClass; + List dependencies; + List repositories; + List resolvedDependencies; + String javaVersion; + String requestedJavaVersion; + String availableJdkPath; + List compileOptions; + List runtimeOptions; + List files; + List sources; + String description; + String gav; + String module; + Map> docs; + + public ScriptInfo(Project prj, Path buildDir, boolean assureJdkInstalled) { + originalResource = prj.getResourceRef().getOriginalResource(); + + if (scripts.add(originalResource)) { + backingResource = prj.getResourceRef().getFile().toString(); + + init(prj); + + try { + BuildContext ctx = BuildContext.forProject(prj, buildDir); + init(ctx); + } catch (Exception e) { + Util.warnMsg("Unable to obtain full information, the script probably contains errors", e); + } - try { - JdkManager jdkMan = JavaUtil.defaultJdkManager(); - Jdk jdk = assureJdkInstalled ? jdkMan.getOrInstallJdk(requestedJavaVersion) - : jdkMan.getJdk(requestedJavaVersion); - if (jdk != null && jdk.isInstalled()) { - availableJdkPath = ((Jdk.InstalledJdk) jdk).home().toString(); + try { + JdkManager jdkMan = JavaUtil.defaultJdkManager(); + Jdk jdk = assureJdkInstalled ? jdkMan.getOrInstallJdk(requestedJavaVersion) + : jdkMan.getJdk(requestedJavaVersion); + if (jdk != null && jdk.isInstalled()) { + availableJdkPath = ((Jdk.InstalledJdk) jdk).home().toString(); + } + } catch (ExitException e) { + // Ignore } - } catch (ExitException e) { - // Ignore } } - } - private void init(Project prj) { - if (prj.getMainSource() == null) { + private void init(Project prj) { + if (prj.getMainSource() == null) { + if (!prj.getRepositories().isEmpty()) { + repositories = prj.getRepositories() + .stream() + .map(Repo::new) + .collect(Collectors.toList()); + } + } else { + init(prj.getMainSourceSet()); + } if (!prj.getRepositories().isEmpty()) { repositories = prj.getRepositories() .stream() .map(Repo::new) .collect(Collectors.toList()); } - } else { - init(prj.getMainSourceSet()); - } - if (!prj.getRepositories().isEmpty()) { - repositories = prj.getRepositories() - .stream() - .map(Repo::new) - .collect(Collectors.toList()); - } - gav = prj.getGav().orElse(null); - description = prj.getDescription().orElse(null); - docs = getDocsMap(prj.getDocs()); + gav = prj.getGav().orElse(null); + description = prj.getDescription().orElse(null); + docs = getDocsMap(prj.getDocs()); - module = prj.getModuleName().orElse(null); + module = prj.getModuleName().orElse(null); - mainClass = prj.getMainClass(); - module = ModuleUtil.getModuleName(prj); - requestedJavaVersion = prj.getJavaVersion(); + mainClass = prj.getMainClass(); + module = ModuleUtil.getModuleName(prj); + requestedJavaVersion = prj.getJavaVersion(); - if (prj.getJavaVersion() != null) { - javaVersion = Integer.toString(JavaUtil.parseJavaVersion(prj.getJavaVersion())); - } + if (prj.getJavaVersion() != null) { + javaVersion = Integer.toString(JavaUtil.parseJavaVersion(prj.getJavaVersion())); + } - List opts = prj.getRuntimeOptions(); - if (!opts.isEmpty()) { - runtimeOptions = opts; + List opts = prj.getRuntimeOptions(); + if (!opts.isEmpty()) { + runtimeOptions = opts; + } } - } - private void init(SourceSet ss) { - List deps = ss.getDependencies(); - if (!deps.isEmpty()) { - dependencies = deps; - } - List refs = ss.getResources(); - if (!refs.isEmpty()) { - files = refs.stream() - .map(ProjectFile::new) - .collect(Collectors.toList()); - } - List srcs = ss.getSources(); - if (!srcs.isEmpty()) { - sources = srcs.stream() - .map(ProjectFile::new) - .collect(Collectors.toList()); - } - if (!ss.getCompileOptions().isEmpty()) { - compileOptions = ss.getCompileOptions(); + private void init(SourceSet ss) { + List deps = ss.getDependencies(); + if (!deps.isEmpty()) { + dependencies = deps; + } + List refs = ss.getResources(); + if (!refs.isEmpty()) { + files = refs.stream() + .map(ProjectFile::new) + .collect(Collectors.toList()); + } + List srcs = ss.getSources(); + if (!srcs.isEmpty()) { + sources = srcs.stream() + .map(ProjectFile::new) + .collect(Collectors.toList()); + } + if (!ss.getCompileOptions().isEmpty()) { + compileOptions = ss.getCompileOptions(); + } } - } - private void init(BuildContext ctx) { - applicationJar = ctx.getJarFile() == null ? null - : ctx.getJarFile().toAbsolutePath().toString(); - applicationJsa = ctx.getJsaFile() != null && Files.isRegularFile(ctx.getJsaFile()) - ? ctx.getJsaFile().toAbsolutePath().toString() - : null; - nativeImage = ctx.getNativeImageFile() != null && Files.exists(ctx.getNativeImageFile()) - ? ctx.getNativeImageFile().toAbsolutePath().toString() - : null; - - List artifacts = ctx.resolveClassPath().getArtifacts(); - if (artifacts.isEmpty()) { - resolvedDependencies = Collections.emptyList(); - } else { - resolvedDependencies = artifacts - .stream() - .map(a -> a.getFile().toString()) - .collect(Collectors.toList()); - } + private void init(BuildContext ctx) { + applicationJar = ctx.getJarFile() == null ? null + : ctx.getJarFile().toAbsolutePath().toString(); + applicationJsa = ctx.getJsaFile() != null && Files.isRegularFile(ctx.getJsaFile()) + ? ctx.getJsaFile().toAbsolutePath().toString() + : null; + nativeImage = ctx.getNativeImageFile() != null && Files.exists(ctx.getNativeImageFile()) + ? ctx.getNativeImageFile().toAbsolutePath().toString() + : null; + + List artifacts = ctx.resolveClassPath().getArtifacts(); + if (artifacts.isEmpty()) { + resolvedDependencies = Collections.emptyList(); + } else { + resolvedDependencies = artifacts + .stream() + .map(a -> a.getFile().toString()) + .collect(Collectors.toList()); + } - if (ctx.getJarFile() != null && Files.exists(ctx.getJarFile())) { - Project jarProject = Project.builder().build(ctx.getJarFile()); - mainClass = jarProject.getMainClass(); - gav = jarProject.getGav().orElse(gav); - module = ModuleUtil.getModuleName(jarProject); + if (ctx.getJarFile() != null && Files.exists(ctx.getJarFile())) { + Project jarProject = Project.builder().build(ctx.getJarFile()); + mainClass = jarProject.getMainClass(); + gav = jarProject.getGav().orElse(gav); + module = ModuleUtil.getModuleName(jarProject); + } } - } - /** - * Returns a map of documentation ids to lists of documentation references. Refs - * that has no id are grouped under "main". - * - * @param docs the list of documentation references - * @return a map where the key is the documentation id and the value is a list - * of ProjectFile pointing to the documentation files or links - */ - Map> getDocsMap(List docs) { - Map> docsMap = new LinkedHashMap<>(); - if (docs != null) { - for (DocRef doc : docs) { - String key = doc.getId() == null ? "main" : doc.getId(); - List pfs = docsMap.computeIfAbsent(key, k -> new ArrayList<>()); - pfs.add(new ProjectFile(doc.getRef())); + /** + * Returns a map of documentation ids to lists of documentation references. Refs + * that has no id are grouped under "main". + * + * @param docs the list of documentation references + * @return a map where the key is the documentation id and the value is a list + * of ProjectFile pointing to the documentation files or links + */ + Map> getDocsMap(List docs) { + Map> docsMap = new LinkedHashMap<>(); + if (docs != null) { + for (DocRef doc : docs) { + String key = doc.getId() == null ? "main" : doc.getId(); + List pfs = docsMap.computeIfAbsent(key, k -> new ArrayList<>()); + pfs.add(new ProjectFile(doc.getRef())); + } } + return docsMap; } - return docsMap; + } - } + private static Set scripts; - private static Set scripts; + ScriptInfo getInfo(boolean assureJdkInstalled) { + scriptMixin.validate(); - ScriptInfo getInfo(boolean assureJdkInstalled) { - scriptMixin.validate(); + ProjectBuilder pb = createProjectBuilder(); + Project prj = pb.build(scriptMixin.scriptOrFile); - ProjectBuilder pb = createProjectBuilder(); - Project prj = pb.build(scriptMixin.scriptOrFile); + scripts = new HashSet<>(); - scripts = new HashSet<>(); + Path bd = buildDir != null ? Paths.get(buildDir) : null; + return new ScriptInfo(prj, bd, assureJdkInstalled); + } - return new ScriptInfo(prj, buildDir, assureJdkInstalled); - } + ProjectBuilder createProjectBuilder() { + return Project + .builder() + .setProperties(dependencyInfoMixin.getProperties()) + .additionalDependencies(dependencyInfoMixin.getDependencies()) + .additionalRepositories(dependencyInfoMixin.getRepositories()) + .additionalClasspaths(dependencyInfoMixin.getClasspaths()) + .additionalSources(scriptMixin.sources) + .additionalResources(scriptMixin.resources) + .forceType(scriptMixin.getForceType()) + .moduleName(module) + .catalog(scriptMixin.catalog != null ? new java.io.File(scriptMixin.catalog) : null); + } - ProjectBuilder createProjectBuilder() { - return Project - .builder() - .setProperties(dependencyInfoMixin.getProperties()) - .additionalDependencies(dependencyInfoMixin.getDependencies()) - .additionalRepositories(dependencyInfoMixin.getRepositories()) - .additionalClasspaths(dependencyInfoMixin.getClasspaths()) - .additionalSources(scriptMixin.sources) - .additionalResources(scriptMixin.resources) - .forceType(scriptMixin.forceType) - .moduleName(module) - .catalog(scriptMixin.catalog); } -} + @CommandDefinition(name = "tools", description = "Prints a json description usable for tools/IDE's to get classpath and more info for a jbang script/application.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class Tools extends BaseInfoCommand { -@CommandLine.Command(name = "tools", description = "Prints a json description usable for tools/IDE's to get classpath and more info for a jbang script/application. Exact format is still quite experimental.") -class Tools extends BaseInfoCommand { + @Option(name = "select", description = "Indicate the name of the field to select and return from the full info result") + String select; - @CommandLine.Option(names = { - "--select" }, description = "Indicate the name of the field to select and return from the full info result") - String select; + @Override + public Integer doCall() throws IOException { - @Override - public Integer doCall() throws IOException { - - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - ScriptInfo info = getInfo(true); - if (select != null) { - try { - Field f = info.getClass().getDeclaredField(select); - Object v = f.get(info); - if (v != null) { - if (v instanceof String || v instanceof Number) { - out.println(v); + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + ScriptInfo info = getInfo(true); + if (select != null) { + try { + Field f = info.getClass().getDeclaredField(select); + Object v = f.get(info); + if (v != null) { + if (v instanceof String || v instanceof Number) { + out.println(v); + } else { + parser.toJson(v, out); + } } else { - parser.toJson(v, out); + // We'll return an error code for `null` so + // any calling scripts can easily detect that + // situation instead of having to ambiguously + // compare against the string "null" + return EXIT_GENERIC_ERROR; } - } else { - // We'll return an error code for `null` so - // any calling scripts can easily detect that - // situation instead of having to ambiguously - // compare against the string "null" - return EXIT_GENERIC_ERROR; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExitException(EXIT_INVALID_INPUT, "Cannot return value of unknown field: " + select, e); } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new ExitException(EXIT_INVALID_INPUT, "Cannot return value of unknown field: " + select, e); + } else { + parser.toJson(info, out); } - } else { - parser.toJson(info, out); - } - return EXIT_OK; + return EXIT_OK; + } } -} -@CommandLine.Command(name = "classpath", description = "Prints class-path used for this application using operating system specific path separation.") -class ClassPath extends BaseInfoCommand { + @CommandDefinition(name = "classpath", description = "Prints class-path used for this application using operating system specific path separation.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class ClassPath extends BaseInfoCommand { - @CommandLine.Option(names = { - "--deps-only" }, description = "Only include the dependencies in the output, not the application jar itself") - boolean dependenciesOnly; + @Option(name = "deps-only", hasValue = false, description = "Only include the dependencies in the output, not the application jar itself") + boolean dependenciesOnly; - @Override - public Integer doCall() throws IOException { + @Override + public Integer doCall() throws IOException { - ScriptInfo info = getInfo(false); - List cp = new ArrayList<>(info.resolvedDependencies.size() + 1); - if (!dependenciesOnly && info.applicationJar != null - && !info.resolvedDependencies.contains(info.applicationJar)) { - cp.add(info.applicationJar); - } - cp.addAll(info.resolvedDependencies); - out.println(String.join(CP_SEPARATOR, cp)); + ScriptInfo info = getInfo(false); + List cp = new ArrayList<>(info.resolvedDependencies.size() + 1); + if (!dependenciesOnly && info.applicationJar != null + && !info.resolvedDependencies.contains(info.applicationJar)) { + cp.add(info.applicationJar); + } + cp.addAll(info.resolvedDependencies); + out.println(String.join(CP_SEPARATOR, cp)); - return EXIT_OK; + return EXIT_OK; + } } -} -@CommandLine.Command(name = "jar", description = "Prints the path to this application's JAR file.") -class Jar extends BaseInfoCommand { + @CommandDefinition(name = "jar", description = "Prints the path to this application's JAR file.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class Jar extends BaseInfoCommand { - @Override - public Integer doCall() throws IOException { - ScriptInfo info = getInfo(false); - out.println(info.applicationJar); - return EXIT_OK; + @Override + public Integer doCall() throws IOException { + ScriptInfo info = getInfo(false); + out.println(info.applicationJar); + return EXIT_OK; + } } -} -@CommandLine.Command(name = "docs", description = "Open the documentation file in the default browser.") -class Docs extends BaseInfoCommand { + @CommandDefinition(name = "docs", description = "Open the documentation file in the default browser.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class Docs extends BaseInfoCommand { - @Option(names = { - "--open" }, negatable = true, defaultValue = "false", description = "Open the (first) documentation file/link in the default browser") - public boolean open; + @Option(name = "open", hasValue = false, negatable = true, description = "Open the (first) documentation file/link in the default browser") + public boolean open; - @Override - public Integer doCall() throws IOException { + @Override + public Integer doCall() throws IOException { - ScriptInfo info = getInfo(false); + ScriptInfo info = getInfo(false); - ProjectFile[] toOpen = new ProjectFile[1]; + ProjectFile[] toOpen = new ProjectFile[1]; - if (info.description != null) { - out.println(info.description); - } + if (info.description != null) { + out.println(info.description); + } - info.docs.forEach((String id, List docs) -> { - out.println(ConsoleOutput.yellow(id + ":")); - docs.forEach(doc -> { + info.docs.forEach((String id, List docs) -> { + out.println(ConsoleOutput.yellow(id + ":")); + docs.forEach(doc -> { - String uripart = doc.backingResource == null || Util.isURL(doc.originalResource) ? doc.originalResource - : Paths.get(doc.backingResource).toUri().toString(); - String suffix = doc.backingResource != null ? "" : " (not found)"; + String uripart = doc.backingResource == null || Util.isURL(doc.originalResource) + ? doc.originalResource + : Paths.get(doc.backingResource).toUri().toString(); + String suffix = doc.backingResource != null ? "" : " (not found)"; - if (toOpen[0] == null && "main".equals(id) && doc.backingResource != null) { - toOpen[0] = doc; - } + if (toOpen[0] == null && "main".equals(id) && doc.backingResource != null) { + toOpen[0] = doc; + } - out.printf(" %s%s%n", uripart, suffix); + out.printf(" %s%s%n", uripart, suffix); + }); }); - }); - if (toOpen[0] == null) { - Util.infoMsg("No documentation files found"); - return EXIT_OK; - } - if (!open) { - Util.infoMsg("Use --open to open the documentation file in the default browser."); - return EXIT_OK; - } - if (GraphicsEnvironment.isHeadless()) { - Util.infoMsg("Cannot open documentation file in browser in headless mode"); + if (toOpen[0] == null) { + Util.infoMsg("No documentation files found"); + return EXIT_OK; + } + if (!open) { + Util.infoMsg("Use --open to open the documentation file in the default browser."); + return EXIT_OK; + } + if (GraphicsEnvironment.isHeadless()) { + Util.infoMsg("Cannot open documentation file in browser in headless mode"); + return EXIT_OK; + } + try { + Desktop.getDesktop().browse(getDocsUri(toOpen[0])); + } catch (IOException e) { + Util.infoMsg("Documentation file to open not found: " + toOpen[0]); + } + return EXIT_OK; } - try { - Desktop.getDesktop().browse(getDocsUri(toOpen[0])); - } catch (IOException e) { - Util.infoMsg("Documentation file to open not found: " + toOpen[0]); - } - - return EXIT_OK; - } - URI getDocsUri(ProjectFile doc) { - if (Util.isURL(doc.originalResource)) { - return URI.create(doc.originalResource); + URI getDocsUri(ProjectFile doc) { + if (Util.isURL(doc.originalResource)) { + return URI.create(doc.originalResource); + } + return Paths.get(doc.backingResource).toUri(); } - return Paths.get(doc.backingResource).toUri(); - } + } } diff --git a/src/main/java/dev/jbang/cli/Init.java b/src/main/java/dev/jbang/cli/Init.java index 37d4493b4..4781958ff 100644 --- a/src/main/java/dev/jbang/cli/Init.java +++ b/src/main/java/dev/jbang/cli/Init.java @@ -11,6 +11,13 @@ import javax.lang.model.SourceVersion; +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionGroup; +import org.aesh.command.option.OptionList; + import dev.jbang.ai.AIProvider; import dev.jbang.ai.AIProviderFactory; import dev.jbang.catalog.TemplateProperty; @@ -23,52 +30,89 @@ import io.quarkus.qute.Template; import io.quarkus.qute.TemplateInstance; -import picocli.CommandLine; -import picocli.CommandLine.ArgGroup; -@CommandLine.Command(name = "init", description = "Initialize a script.") +@CommandDefinition(name = "init", description = "Initialize a script.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class, helpGroup = "Editing") public class Init extends BaseCommand { - @CommandLine.Mixin - BuildMixin buildMixin; + @Option(shortName = 'j', name = "java", description = "JDK version to use for running the script.") + String javaVersion; + + @Option(name = "ai-provider", description = "AI provider to use") + String aiProvider; + + @Option(name = "ai-api-key", description = "AI API key") + String aiApiKey; - // @CommandLine.Mixin - @ArgGroup(heading = "AI Options for init with a prompt:\n", exclusive = false) - AIOptions aiOptions = new AIOptions(); + @Option(name = "ai-endpoint", description = "AI endpoint URL") + String aiEndpoint; - @CommandLine.Option(names = { "--template", - "-t" }, description = "Init script with a java class useful for scripting") + @Option(name = "ai-model", description = "AI model to use") + String aiModel; + + @Option(shortName = 't', name = "template", description = "Init script with a java class useful for scripting") public String initTemplate; - @CommandLine.Option(names = { - "--force" }, description = "Force overwrite of existing files") + @Option(name = "force", hasValue = false, description = "Force overwrite of existing files") public boolean force; - @CommandLine.Option(names = { "--edit" }, description = "Open editor on the generated file(s)") + @Option(name = "edit", hasValue = false, description = "Open editor on the generated file(s)") public boolean edit; - @CommandLine.Option(names = { "-D" }, description = "set a system property", mapFallbackValue = "true") - public Map properties = new HashMap<>(); + @OptionGroup(shortName = 'D', description = "set a system property") + public Map properties; - @CommandLine.Option(names = { - "--deps" }, converter = CommaSeparatedConverter.class, description = "Add additional dependencies (Use commas to separate them).") + @OptionList(name = "deps", valueSeparator = ',', description = "Add additional dependencies (Use commas to separate them).") List dependencies; - @CommandLine.Parameters(paramLabel = "scriptOrFile", index = "0", description = "A file or URL to a Java code file", arity = "1") + @Argument(description = "A file or URL to a Java code file") String scriptOrFile; - @CommandLine.Parameters(paramLabel = "params", index = "1..*", arity = "0..*", description = "Parameters to pass on to the generation") - List params = new ArrayList<>(); + + @Arguments(description = "Parameters") + List params; + + @Override + public void afterParse() { + super.afterParse(); + if (aiProvider == null) { + aiProvider = System.getenv("JBANG_AI_PROVIDER"); + } + if (aiApiKey == null) { + aiApiKey = System.getenv("JBANG_AI_API_KEY"); + } + if (aiEndpoint == null) { + aiEndpoint = System.getenv("JBANG_AI_ENDPOINT"); + } + if (aiModel == null) { + aiModel = System.getenv("JBANG_AI_MODEL"); + } + } public void requireScriptArgument() { if (scriptOrFile == null) { - throw new IllegalArgumentException("Missing required parameter: ''"); + throw new ExitException(EXIT_INVALID_INPUT, "Missing required parameter: ''"); + } + } + + List params() { + if (params != null && !params.isEmpty()) { + return params; } + return Collections.emptyList(); } @Override public Integer doCall() throws IOException { requireScriptArgument(); + if (initTemplate == null) { + initTemplate = "hello"; + } + + Map propsMap = new HashMap<>(); + if (properties != null) { + propsMap.putAll(properties); + } + dev.jbang.catalog.Template tpl = dev.jbang.catalog.Template.get(initTemplate); if (tpl == null) { throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, @@ -84,18 +128,16 @@ public Integer doCall() throws IOException { String baseName = Util.getBaseName(Paths.get(scriptOrFile).getFileName().toString()); String extension = Util.extension(scriptOrFile); - properties.put("scriptref", scriptOrFile); - properties.put("baseName", baseName); - properties.put("dependencies", dependencies); + propsMap.put("scriptref", scriptOrFile); + propsMap.put("baseName", baseName); + propsMap.put("dependencies", dependencies); - int reqVersion = buildMixin.javaVersion != null ? JavaUtil.minRequestedVersion(buildMixin.javaVersion) + int reqVersion = javaVersion != null ? JavaUtil.minRequestedVersion(javaVersion) : JavaUtil.getCurrentMajorJavaVersion(); - properties.put("requestedJavaVersion", buildMixin.javaVersion); - properties.put("javaVersion", reqVersion); - properties.put("compactSourceFiles", reqVersion >= 25); - // properties.put("magiccontent", "//no gpt response. make sure you ran with - // --preview and OPENAI_API_KEY set"); + propsMap.put("requestedJavaVersion", javaVersion); + propsMap.put("javaVersion", reqVersion); + propsMap.put("compactSourceFiles", reqVersion >= 25); List refTargets = tpl.fileRefs.entrySet() .stream() @@ -109,7 +151,6 @@ public Integer doCall() throws IOException { .collect(Collectors.toList()); if (!force) { - // Check if any of the files already exist for (RefTarget refTarget : refTargets) { Path target = refTarget.to(outDir); if (Files.exists(target)) { @@ -119,22 +160,18 @@ public Integer doCall() throws IOException { } } - if (!params.isEmpty()) { - - AIProvider provider = new AIProviderFactory(aiOptions.provider, aiOptions.apiKey, - aiOptions.endpoint, - aiOptions.model) + if (!params().isEmpty()) { + AIProvider provider = new AIProviderFactory(aiProvider, aiApiKey, + aiEndpoint, aiModel) .createProvider(); if (provider != null) { try { Util.infoMsg("JBang AI activated, using " + provider.getName() + ":" + provider.getModel() + " for init. Have a bit of patience - Ctrl+C to abort."); - String response = provider.generateCode(baseName, extension, String.join(" ", params), + String response = provider.generateCode(baseName, extension, String.join(" ", params()), "" + reqVersion); - // sometimes gpt adds a markdown ```java block so lets remove all lines starting - // with ``` in the output. response = response.replaceAll("(?m)^```.*(?:\r?\n|$)", ""); - properties.put("magiccontent", response); + propsMap.put("magiccontent", response); } catch (IllegalStateException | IOException e) { Util.errorMsg( "Failed to generate code with " + provider.getName(), e); @@ -146,20 +183,18 @@ public Integer doCall() throws IOException { } } - applyTemplateProperties(tpl); + applyTemplateProperties(tpl, propsMap); try { for (RefTarget refTarget : refTargets) { if (refTarget.getSource().getOriginalResource().endsWith(".qute")) { - // TODO fix outFile path handling Path out = refTarget.to(outDir); - renderQuteTemplate(out, refTarget.getSource(), properties); + renderQuteTemplate(out, refTarget.getSource(), propsMap); } else { refTarget.copy(outDir); } } } catch (IOException e) { - // Clean up any files we already created for (RefTarget refTarget : refTargets) { Util.deletePath(refTarget.to(outDir), true); } @@ -169,11 +204,7 @@ public Integer doCall() throws IOException { if (edit) { info("File initialized. Opening editor for you. You can also now run it with 'jbang " + renderedScriptOrFile); - // TODO: quick hack that gets the job of opening editor done; but really should - // make a isolated api to open editor instead of invoking subcommand. - // nice thing wit this is that it will honor you jbang config for edit - // automatically. - JBang.getCommandLine().execute("edit", renderedScriptOrFile); + JBang.execute("edit", renderedScriptOrFile); } else { info("File initialized. You can now run it with 'jbang " + renderedScriptOrFile + "' or edit it using 'jbang edit --open=[editor] " @@ -185,11 +216,11 @@ public Integer doCall() throws IOException { return EXIT_OK; } - private void applyTemplateProperties(dev.jbang.catalog.Template tpl) { + private void applyTemplateProperties(dev.jbang.catalog.Template tpl, Map propsMap) { if (tpl.properties != null) { for (Map.Entry entry : tpl.properties.entrySet()) { if (entry.getValue().getDefaultValue() != null) { - properties.putIfAbsent(entry.getKey(), entry.getValue().getDefaultValue()); + propsMap.putIfAbsent(entry.getKey(), entry.getValue().getDefaultValue()); } } } @@ -216,7 +247,7 @@ static Path resolveBaseName(String refTarget, String refSource, String outName) return Paths.get(result); } - void renderQuteTemplate(Path outFile, ResourceRef templateRef, Map properties) + void renderQuteTemplate(Path outFile, ResourceRef templateRef, Map propsMap) throws IOException { Template template = TemplateEngine.instance().getTemplate(templateRef); if (template == null) { @@ -235,8 +266,8 @@ void renderQuteTemplate(Path outFile, ResourceRef templateRef, Map handle(CommandLine.ParseResult parseResult) throws CommandLine.ExecutionException { - Util.verboseMsg("jbang version " + Util.getJBangVersion()); - Future versionCheckResult = VersionChecker.newerVersionAsync(); - List result = super.handle(parseResult); - VersionChecker.informOrCancel(versionCheckResult); - return result; - } - }; - - static CommandLine.IDefaultValueProvider defaultValueProvider = new CommandLine.IDefaultValueProvider() { - @Override - public String defaultValue(CommandLine.Model.ArgSpec argSpec) { - String val = null; - if (argSpec.isOption() - && argSpec.defaultValue() == null - && Util.isNullOrEmptyString(((CommandLine.Model.OptionSpec) argSpec).fallbackValue())) { - String key = argSpecKey(argSpec); - // We skip all "app install" options - if (!key.startsWith("app.install.")) { - // First we check the full name, eg "app.list.format" - val = getValue(key); - if (val == null) { - // Finally we check the option name only, eg "format" - val = getValue(argOptName(argSpec)); - } + public static int execute(String... args) { + try { + String[] newArgs = Main.handleDefaultRun(args); + CommandResult result = AeshRuntimeRunner.builder() + .command(JBang.class) + .args(newArgs) + .execute(); + return result != null ? result.getResultValue() : BaseCommand.EXIT_OK; + } catch (ExitException e) { + return e.getStatus(); + } catch (RuntimeException e) { + Throwable cause = e.getCause(); + Throwable deepest = e; + while (cause != null) { + if (cause instanceof ExitException) { + return ((ExitException) cause).getStatus(); } + deepest = cause; + cause = cause.getCause(); } - return val; - } - - private String getValue(String key) { - String val = null; - String propkey = "jbang." + key; - if (System.getProperties().containsValue(propkey)) { - val = System.getProperty(propkey); - } else { - Configuration cfg = Configuration.instance(); - if (cfg.containsKey(key)) { - val = Objects.toString(cfg.get(key)); - } + if (deepest instanceof RuntimeException) { + throw (RuntimeException) deepest; } - return val; - } - }; - - static String argSpecKey(CommandLine.Model.ArgSpec argSpec) { - List cmdNames = names(argSpec.command()); - cmdNames.add(argOptName(argSpec)); - return String.join(".", cmdNames); - } - - static String argOptName(CommandLine.Model.ArgSpec argSpec) { - return stripDashes(((CommandLine.Model.OptionSpec) argSpec).longestName()).replace("-", ""); - } - - private static List names(CommandSpec cmd) { - List result = new ArrayList<>(); - while (!cmd.name().equalsIgnoreCase("jbang")) { - result.add(0, cmd.name()); - cmd = cmd.parent(); + throw e; } - return result; } - private static String stripDashes(String name) { - if (name.startsWith("--")) { - return name.substring(2); - } else if (name.startsWith("-")) { - return name.substring(1); - } else { - return name; + @SuppressWarnings("unchecked") + public static T parseCommand(String... args) { + try { + String[] newArgs = Main.handleDefaultRun(args); + CommandRegistry registry = AeshCommandRegistryBuilder.builder().command(JBang.class).create(); + org.aesh.command.CommandRuntime runtime = AeshCommandRuntimeBuilder.builder() + .commandRegistry(registry) + .build(); + String commandName = (String) registry.getAllCommandNames().iterator().next(); + Executor executor = runtime.buildExecutor(commandName, newArgs); + Execution execution = executor.getExecutions().get(executor.getExecutions().size() - 1); + execution.populateCommand(); + T cmd = (T) execution.getCommand(); + // Workaround: aesh inherited options are propagated via parser-level + // reflection but afterParse() may not fire reliably in the + // buildExecutor path. Scan raw args for global flags as fallback. + applyParentFlags(newArgs); + return cmd; + } catch (Exception e) { + throw new RuntimeException("Failed to parse command", e); } } - static class ConfigurationResourceBundle extends ResourceBundle { - - private static final String PREFIX = "default."; - - @Override - protected Object handleGetObject(String propkey) { - if (propkey.startsWith(PREFIX)) { - String key = propkey.substring(PREFIX.length()); - return Configuration.instance().get(key); - } else { - return null; - } - } - - @Override - public Enumeration getKeys() { - return Collections.enumeration(Configuration.instance() - .flatten() - .keySet() - .stream() - .map(k -> "default." + k) - .collect(Collectors.toSet())); - } - } - - public static CommandLine getCommandLine(PrintWriter localout, PrintWriter localerr) { - CommandLine cl = new CommandLine(new JBang()); - - cl.getHelpSectionMap().remove(SECTION_KEY_COMMAND_LIST_HEADING); - cl.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, getCommandRenderer()); - - return cl.setExitCodeExceptionMapper(exitCodeExceptionMapper) - .setExecutionExceptionHandler(executionExceptionHandler) - .setParameterExceptionHandler(new DeprecatedMessageHandler(cl.getParameterExceptionHandler())) - .setExecutionStrategy(executionStrategy) - .setDefaultValueProvider(defaultValueProvider) - .setResourceBundle(new ConfigurationResourceBundle()) - .setStopAtPositional(true) - .setAllowOptionsAsOptionParameters(true) - .setAllowSubcommandsAsOptionParameters(true) - .setOut(localout) - .setErr(localerr); - } - - public static CommandGroupRenderer getCommandRenderer() { - return new CommandGroupRenderer(); - } - - public static class CommandGroupRenderer implements CommandLine.IHelpSectionRenderer { - private Map> sections; - private Map externals; - - public CommandGroupRenderer() { - } - - private Map> sections() { - if (sections == null) { - sections = new LinkedHashMap<>(); - sections.put("Essentials", asList("run", "build")); - sections.put("Editing", asList("init", "edit", "deps")); - sections.put("Caching", asList("cache", "export", "jdk")); - sections.put("Configuration", asList("config", "trust", "alias", "template", "catalog", "app")); - sections.put("Other", asList("completion", "info", "version", "wrapper")); - Map cmds = externals(); - if (!cmds.isEmpty()) { - sections.put("External", new ArrayList<>(cmds.keySet())); - } - } - return sections; - } - - private Map externals() { - if (externals == null) { - externals = findExternalCommands(); - } - return externals; - } - - /** - * validate all commands in Help is covered by section and each section command - * exist in help. - * - * @param help - */ - public void validate(CommandLine.Help help, boolean ignoreExternalCommands) { - Set cmds = new HashSet<>(); - sections().forEach((key, value) -> { - if (ignoreExternalCommands && "External".equals(key)) { - return; - } else { - cmds.addAll(value); - } - }); - - Set actualcmds = new HashSet<>(help.subcommands().keySet()); - - actualcmds.removeAll(cmds); - - cmds.removeAll(help.subcommands().keySet()); - - if (cmds.size() > 0) { - throw new IllegalStateException("Section help defined for non existent commands" + cmds); - } - - if (actualcmds.size() > 0) { - throw new IllegalStateException(("Commands found with no assigned section" + actualcmds)); - } - } - - @Override - public String render(CommandLine.Help help) { - if (help.commandSpec().subcommands().isEmpty()) { - return ""; - } - - StringBuilder result = new StringBuilder(); - - sections().forEach((key, value) -> result.append(renderSection(key, value, help))); - return result.toString(); - } - - private String renderSection(String sectionHeading, List cmdNames, CommandLine.Help help) { - TextTable textTable = createTextTable(help); - - if (!sectionHeading.equals("External")) { - for (String name : cmdNames) { - CommandSpec sub = help.commandSpec().subcommands().get(name).getCommandSpec(); - - // create comma-separated list of command name and aliases - String names = sub.names().toString(); - names = names.substring(1, names.length() - 1); // remove leading '[' and trailing ']' - - // description may contain line separators; use Text::splitLines to handle this - String description = description(sub.usageMessage()); - addCommand(textTable, names, description, help); - } - } else { - for (String name : externals().keySet()) { - String description = externals().get(name); - addCommand(textTable, name, description, help); - } - } - - return help.createHeading("%n" + sectionHeading + ":%n") + textTable; - } - - private TextTable createTextTable(CommandLine.Help help) { - CommandSpec spec = help.commandSpec(); - // prepare layout: two columns - // the left column overflows, the right column wraps if text is too long - int commandLength = maxLength(spec.subcommands(), 37); - TextTable textTable = TextTable.forColumns(help.colorScheme(), - new CommandLine.Help.Column(commandLength + 2, 2, SPAN), - new CommandLine.Help.Column(spec.usageMessage().width() - (commandLength + 2), 2, WRAP)); - textTable.setAdjustLineBreaksForWideCJKCharacters( - spec.usageMessage().adjustLineBreaksForWideCJKCharacters()); - return textTable; - } - - private int maxLength(Map subcommands, int max) { - int result = subcommands.values() - .stream() - .map(cmd -> cmd.getCommandSpec().names().toString().length() - 2) - .max(Integer::compareTo) - .get(); - return Math.min(max, result); - } - - private String description(UsageMessageSpec usageMessage) { - if (usageMessage.header().length > 0) { - return usageMessage.header()[0]; - } - if (usageMessage.description().length > 0) { - return usageMessage.description()[0]; - } - return ""; - } - - private void addCommand(TextTable textTable, String name, String description, CommandLine.Help help) { - CommandLine.Help.Ansi.Text[] lines = help.colorScheme() - .text(description != null ? description : "") - .splitLines(); - for (int i = 0; i < lines.length; i++) { - CommandLine.Help.Ansi.Text cmdNamesText = help.colorScheme().commandText(i == 0 ? name : ""); - textTable.addRowValues(cmdNamesText, lines[i]); - } - } - - private static Map findExternalCommands() { - Map result = new TreeMap<>(); - // Add any aliases whose names start with "jbang-" to the result - try { - dev.jbang.catalog.Catalog cat = dev.jbang.catalog.Catalog.getMerged(true, false); - for (String name : cat.aliases.keySet()) { - if (name.startsWith("jbang-")) { - result.put(name.substring(6), cat.aliases.get(name).description); - } - } - } catch (Exception ex) { - Util.verboseMsg("Error trying to list aliases", ex); - } - // Now add any commands found on the PATH whose names start with "jbang-" - // but only if they don't already exist (catalog aliases take precedence) - try { - List paths = getPluginPaths(); - List cmds = findCommandsWith(paths, p -> p.getFileName().toString().startsWith("jbang-")); - for (Path p : cmds) { - result.put(Util.base(p.getFileName().toString()).substring(6), null); - } - } catch (Exception ex) { - Util.verboseMsg("Error trying to list jbang-commands", ex); - } - return result; - } - - private static List getPluginPaths() { - return Arrays.stream(System.getenv().getOrDefault("PATH", "").split(File.pathSeparator)) - .filter(Util::isValidPath) - .map(Paths::get) - .filter(Files::isDirectory) - .collect(Collectors.toList()); - - } - - private static List findCommandsWith(List pathElems, Predicate accept) { - return pathElems.stream() - .filter(Files::isDirectory) - .flatMap(dir -> listFiles(dir).filter(Util::isExecutable).filter(accept)) - .collect(Collectors.toList()); - } - - private static Stream listFiles(Path dir) { - if (Files.isDirectory(dir)) { - try { - return Files.list(dir); - } catch (IOException e) { - throw new RuntimeException(e.getMessage(), e); - } - } else { - return Stream.empty(); + private static void applyParentFlags(String[] args) { + for (String arg : args) { + if ("--".equals(arg)) { + break; + } + switch (arg) { + case "--verbose": + Util.setVerbose(true); + break; + case "--quiet": + Util.setQuiet(true); + break; + case "--offline": + case "-o": + Util.setOffline(true); + break; + case "--fresh": + Util.setFresh(true); + break; + case "--preview": + Util.setPreview(true); + break; + case "--stacktrace": + case "-x": + Util.setPrintExceptions(true); + break; } } } diff --git a/src/main/java/dev/jbang/cli/JBangDefaultValueProvider.java b/src/main/java/dev/jbang/cli/JBangDefaultValueProvider.java new file mode 100644 index 000000000..08a59639d --- /dev/null +++ b/src/main/java/dev/jbang/cli/JBangDefaultValueProvider.java @@ -0,0 +1,62 @@ +package dev.jbang.cli; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.aesh.command.DefaultValueProvider; +import org.aesh.command.impl.internal.ProcessedOption; + +import dev.jbang.Configuration; + +public class JBangDefaultValueProvider implements DefaultValueProvider { + + private static final Set FALLBACK_OPTIONS = new HashSet<>(Arrays.asList("debug", "jfr")); + + @Override + public String defaultValue(ProcessedOption option) { + if (option.name() == null) { + return null; + } + + // Options that act as feature toggles with config-based fallbacks + // handled in resolveAfterParse() — skip them here to avoid + // activating the feature when the user didn't request it. + if (FALLBACK_OPTIONS.contains(option.name())) { + return null; + } + + String optName = option.name().replace("-", ""); + String commandName = option.parent() != null ? option.parent().name() : null; + + if (commandName != null) { + // Skip all "app install" options (matches old picocli behavior) + if ("install".equals(commandName)) { + return null; + } + + // Check the command-qualified key, e.g. "list.format" + String key = commandName + "." + optName; + String val = getValue(key); + if (val != null) { + return val; + } + } + + // Fall back to option-name-only, e.g. "format" + return getValue(optName); + } + + private static String getValue(String key) { + String propkey = "jbang." + key; + if (System.getProperties().containsKey(propkey)) { + return System.getProperty(propkey); + } + Configuration cfg = Configuration.instance(); + if (cfg.containsKey(key)) { + return Objects.toString(cfg.get(key)); + } + return null; + } +} diff --git a/src/main/java/dev/jbang/cli/Jdk.java b/src/main/java/dev/jbang/cli/Jdk.java index c46655ade..db0920729 100644 --- a/src/main/java/dev/jbang/cli/Jdk.java +++ b/src/main/java/dev/jbang/cli/Jdk.java @@ -10,6 +10,13 @@ import java.util.*; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; @@ -20,112 +27,15 @@ import dev.jbang.util.JavaUtil; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "jdk", description = "Manage Java Development Kits installed by jbang.") -public class Jdk { - - @CommandLine.Mixin - HelpMixin helpMixin; - - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; - - @CommandLine.Mixin - JdkProvidersMixin jdkProvidersMixin; - - @CommandLine.Command(name = "install", aliases = "i", description = "Installs a JDK.") - public Integer install( - @CommandLine.Option(names = { "--force", - "-f" }, description = "Force installation even when already installed") boolean force, - @CommandLine.Parameters(paramLabel = "versionOrId", index = "0", description = "The version or id to install", arity = "1") String versionOrId, - @CommandLine.Parameters(paramLabel = "existingJdkPath", index = "1", description = "Pre installed JDK path", arity = "0..1") String path) - throws IOException { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk jdk = jdkMan.getInstalledJdk(versionOrId, JdkProvider.Predicates.canUpdate); - if (force || jdk == null) { - if (!Util.isNullOrBlankString(path)) { - jdkMan.linkToExistingJdk(Paths.get(path), versionOrId); - } else { - if (jdk == null) { - jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); - if (jdk == null) { - throw new IllegalArgumentException("JDK is not available for installation: " + versionOrId); - } - } - if (!jdk.isInstalled()) { - ((dev.jbang.devkitman.Jdk.AvailableJdk) jdk).install(); - } - } - } else { - Util.infoMsg("JDK is already installed: " + jdk); - Util.infoMsg("Use --force to install anyway"); - } - return EXIT_OK; - } +@GroupCommandDefinition(name = "jdk", description = "Manage Java Development Kits installed by jbang.", groupCommands = { + Jdk.JdkInstall.class, Jdk.JdkList.class, Jdk.JdkUninstall.class, + Jdk.JdkHome.class, Jdk.JdkJavaEnv.class, Jdk.JdkExec.class, Jdk.JdkDefault.class }, generateHelp = true, helpGroup = "Caching", defaultValueProvider = JBangDefaultValueProvider.class) +public class Jdk extends BaseCommand { - @CommandLine.Command(name = "list", aliases = "l", description = "Lists installed JDKs.") - public Integer list( - @CommandLine.Option(names = { - "--available" }, description = "Shows versions available for installation") boolean available, - @CommandLine.Option(names = { - "--show-details" }, description = "Shows detailed information for each JDK (only when format=text)") boolean details, - @CommandLine.Option(names = { - "--format" }, description = "Specify output format ('text' or 'json')") FormatMixin.Format format) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk defaultJdk = jdkMan.getDefaultJdk(); - int defMajorVersion = defaultJdk != null ? defaultJdk.majorVersion() : 0; - PrintStream out = System.out; - List jdks; - if (available) { - jdks = jdkMan.listAvailableJdks(); - } else { - jdks = jdkMan.listInstalledJdks(); - } - List jdkOuts = jdks.stream() - .map(jdk -> new JdkOut(jdk.id(), jdk.version(), jdk.provider().name(), - jdk.isInstalled() ? ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home() : null, - details ? jdk.equals(defaultJdk) - : jdk.majorVersion() == defMajorVersion)) - .collect(Collectors.toList()); - if (!details) { - // Only keep a list of unique major versions - Set uniqueJdks = new TreeSet<>(Comparator.comparingInt(j -> j.version)); - uniqueJdks.addAll(jdkOuts); - jdkOuts = new ArrayList<>(uniqueJdks); - } - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(jdkOuts, out); - } else { - if (!jdkOuts.isEmpty()) { - if (!available) { - out.println("Installed JDKs (<=default):"); - } - jdkOuts.forEach(jdk -> { - out.print(" "); - out.print(jdk.version); - out.print(" ("); - out.print(jdk.fullVersion); - if (details) { - out.print(", " + jdk.providerName + ", " + jdk.id); - if (jdk.javaHomeDir != null) { - out.print(", " + jdk.javaHomeDir); - } - } - out.print(")"); - if (!available) { - if (Boolean.TRUE.equals(jdk.isDefault)) { - out.print(" <"); - } - } - out.println(); - }); - } else { - out.printf("No JDKs %s%n", available ? "available" : "installed"); - } - } - return EXIT_OK; + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'jdk'"); + return EXIT_INVALID_INPUT; } static class JdkOut implements Comparable { @@ -160,141 +70,301 @@ public int compareTo(JdkOut o) { } } - @CommandLine.Command(name = "uninstall", aliases = "u", description = "Uninstalls an existing JDK.") - public Integer uninstall( - @CommandLine.Parameters(paramLabel = "version", index = "0", description = "The version to install", arity = "1") String versionOrId) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getInstalledJdk(versionOrId, - JdkProvider.Predicates.canUpdate); - if (jdk == null) { - throw new ExitException(EXIT_INVALID_INPUT, "JDK " + versionOrId + " is not installed"); - } - jdkMan.uninstallJdk(jdk); - Util.infoMsg("Uninstalled JDK:\n " + versionOrId); - return EXIT_OK; - } + @CommandDefinition(name = "install", description = "Installs a JDK.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkInstall extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Option(shortName = 'f', name = "force", hasValue = false, description = "Force installation even when already installed") + boolean force; + + @Argument(description = "The version or id to install", required = true) + String versionOrId; - @CommandLine.Command(name = "home", description = "Prints the folder where the given JDK is installed.") - public Integer home( - @CommandLine.Parameters(paramLabel = "versionOrId", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getOrInstallJdk(versionOrId); - if (jdk.isInstalled()) { - Path home = jdk.home(); - String homeStr = Util.pathToString(home); - System.out.println(homeStr); + @Option(name = "path", description = "Pre installed JDK path") + String path; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk jdk = jdkMan.getInstalledJdk(versionOrId, JdkProvider.Predicates.canUpdate); + if (force || jdk == null) { + if (!Util.isNullOrBlankString(path)) { + jdkMan.linkToExistingJdk(Paths.get(path), versionOrId); + } else { + if (jdk == null) { + jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); + if (jdk == null) { + throw new ExitException(EXIT_INVALID_INPUT, "JDK is not available for installation: " + versionOrId); + } + } + if (!jdk.isInstalled()) { + ((dev.jbang.devkitman.Jdk.AvailableJdk) jdk).install(); + } + } + } else { + Util.infoMsg("JDK is already installed: " + jdk); + Util.infoMsg("Use --force to install anyway"); + } + return EXIT_OK; } - return EXIT_OK; } - @CommandLine.Command(name = "java-env", aliases = "env", description = "Prints out the environment variables needed to use the given JDK.") - public Integer javaEnv( - @CommandLine.Parameters(paramLabel = "versionOrId", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk jdk = null; - if (versionOrId != null && JavaUtil.isRequestedVersion(versionOrId)) { - jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); - } - if (jdk == null || !jdk.isInstalled()) { - jdk = jdkMan.getOrInstallJdk(versionOrId); - } - if (jdk.isInstalled()) { - Path home = ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home(); - String homeStr = Util.pathToString(home); - String homeOsStr = Util.pathToOsString(home); + @CommandDefinition(name = "list", description = "Lists installed JDKs.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkList extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Option(name = "available", hasValue = false, description = "Shows versions available for installation") + boolean available; + + @Option(name = "show-details", hasValue = false, description = "Shows detailed information for each JDK (only when format=text)") + boolean details; + + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk defaultJdk = jdkMan.getDefaultJdk(); + int defMajorVersion = defaultJdk != null ? defaultJdk.majorVersion() : 0; PrintStream out = System.out; - switch (Util.getShell()) { - case bash: - // Not using `println()` here because it will output /n/r - // on Windows which causes problems - out.print("export PATH=\"" + homeStr + "/bin:$PATH\"\n"); - out.print("export JAVA_HOME=\"" + homeOsStr + "\"\n"); - out.print("# Run this command to configure your shell:\n"); - out.print("# eval $(jbang jdk java-env"); - if (versionOrId != null) { - out.print(" " + versionOrId); - } - out.print(")\n"); - break; - case cmd: - out.println("set PATH=" + homeStr + "\\bin;%PATH%"); - out.println("set JAVA_HOME=" + homeOsStr); - out.println("rem Copy & paste the above commands in your CMD window or add"); - out.println("rem them to your Environment Variables in the System Settings."); - break; - case powershell: - out.println("$env:PATH=\"" + homeStr + "\\bin;$env:PATH\""); - out.println("$env:JAVA_HOME=\"" + homeOsStr + "\""); - out.println("# Run this command to configure your environment:"); - out.print("# jbang jdk java-env"); - if (versionOrId != null) { - out.print(" " + versionOrId); + List jdks; + if (available) { + jdks = jdkMan.listAvailableJdks(); + } else { + jdks = jdkMan.listInstalledJdks(); + } + List jdkOuts = jdks.stream() + .map(jdk -> new JdkOut(jdk.id(), jdk.version(), jdk.provider().name(), + jdk.isInstalled() ? ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home() : null, + details ? jdk.equals(defaultJdk) + : jdk.majorVersion() == defMajorVersion)) + .collect(Collectors.toList()); + if (!details) { + // Only keep a list of unique major versions + Set uniqueJdks = new TreeSet<>(Comparator.comparingInt(j -> j.version)); + uniqueJdks.addAll(jdkOuts); + jdkOuts = new ArrayList<>(uniqueJdks); + } + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(jdkOuts, out); + } else { + if (!jdkOuts.isEmpty()) { + if (!available) { + out.println("Installed JDKs (<=default):"); + } + jdkOuts.forEach(jdk -> { + out.print(" "); + out.print(jdk.version); + out.print(" ("); + out.print(jdk.fullVersion); + if (details) { + out.print(", " + jdk.providerName + ", " + jdk.id); + if (jdk.javaHomeDir != null) { + out.print(", " + jdk.javaHomeDir); + } + } + out.print(")"); + if (!available) { + if (Boolean.TRUE.equals(jdk.isDefault)) { + out.print(" <"); + } + } + out.println(); + }); + } else { + out.printf("No JDKs %s%n", available ? "available" : "installed"); } - out.println(" | iex"); - break; } + return EXIT_OK; } - return EXIT_OK; } - @CommandLine.Command(name = "exec", aliases = "x", description = "Executes the given command using the default (or specified) JDK.") - public Integer exec( - @CommandLine.Option(names = { "-j", - "--java" }, description = "JDK version to use for executing the command.") String versionOrId, - @CommandLine.Parameters(index = "0..*", arity = "1..*", description = "Command to execute") List args) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - dev.jbang.devkitman.Jdk jdk = null; - if (versionOrId != null && JavaUtil.isRequestedVersion(versionOrId)) { - jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); + @CommandDefinition(name = "uninstall", description = "Uninstalls an existing JDK.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkUninstall extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Argument(description = "The version to uninstall", required = true) + String versionOrId; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getInstalledJdk(versionOrId, + JdkProvider.Predicates.canUpdate); + if (jdk == null) { + throw new ExitException(EXIT_INVALID_INPUT, "JDK " + versionOrId + " is not installed"); + } + jdkMan.uninstallJdk(jdk); + Util.infoMsg("Uninstalled JDK:\n " + versionOrId); + return EXIT_OK; } - if (jdk == null || !jdk.isInstalled()) { - jdk = jdkMan.getOrInstallJdk(versionOrId); + } + + @CommandDefinition(name = "home", description = "Prints the folder where the given JDK is installed.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkHome extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Argument(description = "The version of the JDK to select") + String versionOrId; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getOrInstallJdk(versionOrId); + if (jdk.isInstalled()) { + Path home = jdk.home(); + String homeStr = Util.pathToString(home); + System.out.println(homeStr); + } + return EXIT_OK; } - if (jdk.isInstalled()) { - Path home = ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home(); - String fullCmd = CommandBuffer.of(args).asCommandLine(); - if (Util.getShell() == Util.Shell.bash) { - fullCmd = "env PATH=\"" + home + File.separator + "bin:$PATH\" JAVA_HOME='" + home + "' " - + fullCmd; - } else if (Util.getShell() == Util.Shell.powershell) { - fullCmd = "{ $oldPath, $env:PATH, $oldHome, $env:JAVA_HOME=$env:PATH, \"" + home - + "\\bin;$env:PATH\", $env:JAVA_HOME, '" + home + "' ; " + fullCmd - + " ; $env:PATH, $env:JAVA_HOME=$oldPath, $oldHome }"; - } else { - String path = home + "\\bin;" + System.getenv("PATH"); - fullCmd = "set \"PATH=" + path + "\" && set \"JAVA_HOME=" + home + "\" && " + fullCmd; + } + + @CommandDefinition(name = "java-env", description = "Prints out the environment variables needed to use the given JDK.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkJavaEnv extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Argument(description = "The version of the JDK to select") + String versionOrId; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk jdk = null; + if (versionOrId != null && JavaUtil.isRequestedVersion(versionOrId)) { + jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); } - Util.verboseMsg("Executing in Java environment: " + fullCmd); - System.out.println(fullCmd); - return EXIT_EXECUTE; + if (jdk == null || !jdk.isInstalled()) { + jdk = jdkMan.getOrInstallJdk(versionOrId); + } + if (jdk.isInstalled()) { + Path home = ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home(); + String homeStr = Util.pathToString(home); + String homeOsStr = Util.pathToOsString(home); + PrintStream out = System.out; + switch (Util.getShell()) { + case bash: + // Not using `println()` here because it will output /n/r + // on Windows which causes problems + out.print("export PATH=\"" + homeStr + "/bin:$PATH\"\n"); + out.print("export JAVA_HOME=\"" + homeOsStr + "\"\n"); + out.print("# Run this command to configure your shell:\n"); + out.print("# eval $(jbang jdk java-env"); + if (versionOrId != null) { + out.print(" " + versionOrId); + } + out.print(")\n"); + break; + case cmd: + out.println("set PATH=" + homeStr + "\\bin;%PATH%"); + out.println("set JAVA_HOME=" + homeOsStr); + out.println("rem Copy & paste the above commands in your CMD window or add"); + out.println("rem them to your Environment Variables in the System Settings."); + break; + case powershell: + out.println("$env:PATH=\"" + homeStr + "\\bin;$env:PATH\""); + out.println("$env:JAVA_HOME=\"" + homeOsStr + "\""); + out.println("# Run this command to configure your environment:"); + out.print("# jbang jdk java-env"); + if (versionOrId != null) { + out.print(" " + versionOrId); + } + out.println(" | iex"); + break; + } + } + return EXIT_OK; } - return EXIT_OK; } - @CommandLine.Command(name = "default", description = "Sets the default JDK to be used by JBang.") - public Integer defaultJdk( - @CommandLine.Parameters(paramLabel = "version", index = "0", description = "The version of the JDK to select", arity = "0..1") String versionOrId) { - JdkManager jdkMan = jdkProvidersMixin.getJdkManager(); - if (!jdkMan.hasDefaultProvider()) { - Util.warnMsg("Cannot perform operation, the 'default' provider was not found"); - return EXIT_INVALID_INPUT; + @CommandDefinition(name = "exec", description = "Executes the given command using the default (or specified) JDK.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkExec extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Option(shortName = 'j', name = "java", description = "JDK version to use for executing the command.") + String versionOrId; + + @Arguments(description = "Command to execute", required = true) + List args; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + dev.jbang.devkitman.Jdk jdk = null; + if (versionOrId != null && JavaUtil.isRequestedVersion(versionOrId)) { + jdk = jdkMan.getJdk(versionOrId, JdkProvider.Predicates.canUpdate); + } + if (jdk == null || !jdk.isInstalled()) { + jdk = jdkMan.getOrInstallJdk(versionOrId); + } + if (jdk.isInstalled()) { + Path home = ((dev.jbang.devkitman.Jdk.InstalledJdk) jdk).home(); + String fullCmd = CommandBuffer.of(args).asCommandLine(); + if (Util.getShell() == Util.Shell.bash) { + fullCmd = "env PATH=\"" + home + File.separator + "bin:$PATH\" JAVA_HOME='" + home + "' " + + fullCmd; + } else if (Util.getShell() == Util.Shell.powershell) { + fullCmd = "{ $oldPath, $env:PATH, $oldHome, $env:JAVA_HOME=$env:PATH, \"" + home + + "\\bin;$env:PATH\", $env:JAVA_HOME, '" + home + "' ; " + fullCmd + + " ; $env:PATH, $env:JAVA_HOME=$oldPath, $oldHome }"; + } else { + String path = home + "\\bin;" + System.getenv("PATH"); + fullCmd = "set \"PATH=" + path + "\" && set \"JAVA_HOME=" + home + "\" && " + fullCmd; + } + Util.verboseMsg("Executing in Java environment: " + fullCmd); + System.out.println(fullCmd); + return EXIT_EXECUTE; + } + return EXIT_OK; } - dev.jbang.devkitman.Jdk.InstalledJdk defjdk = jdkMan.getDefaultJdk(); - if (versionOrId != null) { - dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getOrInstallJdk(versionOrId); - if (defjdk == null || (!jdk.equals(defjdk) && !Objects.equals(jdk.home(), defjdk.home()))) { - jdkMan.setDefaultJdk(jdk); - } else { - Util.infoMsg("Default JDK already set to " + defjdk.majorVersion()); + } + + @CommandDefinition(name = "default", description = "Sets the default JDK to be used by JBang.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class JdkDefault extends BaseCommand { + + @Mixin + JdkProvidersMixin jdkMixin; + + @Argument(description = "The version of the JDK to select") + String versionOrId; + + @Override + public Integer doCall() throws IOException { + JdkManager jdkMan = jdkMixin.getJdkManager(); + if (!jdkMan.hasDefaultProvider()) { + Util.warnMsg("Cannot perform operation, the 'default' provider was not found"); + return EXIT_INVALID_INPUT; } - } else { - if (defjdk == null) { - Util.infoMsg("No default JDK set, use 'jbang jdk default ' to set one."); + dev.jbang.devkitman.Jdk.InstalledJdk defjdk = jdkMan.getDefaultJdk(); + if (versionOrId != null) { + dev.jbang.devkitman.Jdk.InstalledJdk jdk = jdkMan.getOrInstallJdk(versionOrId); + if (defjdk == null || (!jdk.equals(defjdk) && !Objects.equals(jdk.home(), defjdk.home()))) { + jdkMan.setDefaultJdk(jdk); + } else { + Util.infoMsg("Default JDK already set to " + defjdk.majorVersion()); + } } else { - Util.infoMsg("Default JDK is currently set to " + defjdk.majorVersion()); + if (defjdk == null) { + Util.infoMsg("No default JDK set, use 'jbang jdk default ' to set one."); + } else { + Util.infoMsg("Default JDK is currently set to " + defjdk.majorVersion()); + } } + return EXIT_OK; } - return EXIT_OK; } - } diff --git a/src/main/java/dev/jbang/cli/JdkProvidersMixin.java b/src/main/java/dev/jbang/cli/JdkProvidersMixin.java index 613bf3cea..d56646e54 100644 --- a/src/main/java/dev/jbang/cli/JdkProvidersMixin.java +++ b/src/main/java/dev/jbang/cli/JdkProvidersMixin.java @@ -1,36 +1,28 @@ package dev.jbang.cli; -import java.util.ArrayList; import java.util.List; +import org.aesh.command.option.OptionList; +import org.aesh.command.option.OptionVisibility; + import dev.jbang.devkitman.JdkManager; import dev.jbang.util.JavaUtil; -import picocli.CommandLine; - public class JdkProvidersMixin { - @CommandLine.Option(names = { - "--jdk-providers" }, description = "Use the given providers to check for installed JDKs", split = ",", hidden = true) + @OptionList(name = "jdk-providers", valueSeparator = ',', visibility = OptionVisibility.HIDDEN, description = "Use the given providers to check for installed JDKs") List jdkProviders; private JdkManager jdkMan; - protected JdkManager getJdkManager() { + public List getJdkProviders() { + return jdkProviders; + } + + public JdkManager getJdkManager() { if (jdkMan == null) { jdkMan = JavaUtil.defaultJdkManager(jdkProviders); } return jdkMan; } - - public List opts() { - List opts = new ArrayList<>(); - if (jdkProviders != null) { - for (String p : jdkProviders) { - opts.add("--jdk-providers"); - opts.add(p); - } - } - return opts; - } } diff --git a/src/main/java/dev/jbang/cli/KeyValueConsumer.java b/src/main/java/dev/jbang/cli/KeyValueConsumer.java deleted file mode 100644 index 44c0db274..000000000 --- a/src/main/java/dev/jbang/cli/KeyValueConsumer.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.jbang.cli; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import picocli.CommandLine; - -public class KeyValueConsumer implements CommandLine.IParameterConsumer { - - Pattern p = Pattern.compile("(\\S*?)(=(\\S+))?"); - - @Override - public void consumeParameters(Stack args, CommandLine.Model.ArgSpec argSpec, - CommandLine.Model.CommandSpec commandSpec) { - String arg = args.pop(); - Matcher m = p.matcher(arg); - if (m.matches()) { - Map kv = argSpec.getValue(); - - if (kv == null) { - kv = new LinkedHashMap<>(); - } - - kv.put(m.group(1), m.group(3)); - - argSpec.setValue(kv); - } - } -} diff --git a/src/main/java/dev/jbang/cli/NativeMixin.java b/src/main/java/dev/jbang/cli/NativeMixin.java index ac38c15e2..dcf47aedd 100644 --- a/src/main/java/dev/jbang/cli/NativeMixin.java +++ b/src/main/java/dev/jbang/cli/NativeMixin.java @@ -3,14 +3,15 @@ import java.util.ArrayList; import java.util.List; -import picocli.CommandLine; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionList; public class NativeMixin { - @CommandLine.Option(names = { - "-n", "--native" }, description = "Build using native-image") - Boolean nativeImage; - @CommandLine.Option(names = { "-N", "--native-option" }, description = "Options to pass to the native image tool") + @Option(shortName = 'n', name = "native", hasValue = false, description = "Build using native-image") + public Boolean nativeImage; + + @OptionList(shortName = 'N', name = "native-option", description = "Options to pass to the native image tool") public List nativeOptions; public List opts() { diff --git a/src/main/java/dev/jbang/cli/Run.java b/src/main/java/dev/jbang/cli/Run.java index 75a2f768b..2d7833f1f 100644 --- a/src/main/java/dev/jbang/cli/Run.java +++ b/src/main/java/dev/jbang/cli/Run.java @@ -2,10 +2,14 @@ import java.io.IOException; import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; + import dev.jbang.resources.resolvers.AliasResourceResolver; import dev.jbang.resources.resolvers.LiteralScriptResourceResolver; import dev.jbang.source.BuildContext; @@ -15,34 +19,48 @@ import dev.jbang.source.Source; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "run", description = "Builds and runs provided script. (default command)") +@CommandDefinition(name = "run", description = "Builds and runs provided script. (default command)", generateHelp = true, stopAtFirstPositional = true, helpGroup = "Essentials", defaultValueProvider = JBangDefaultValueProvider.class) public class Run extends BaseBuildCommand { - @CommandLine.Mixin + @Mixin public RunMixin runMixin; - @CommandLine.Option(names = { "-c", - "--code" }, arity = "0..1", description = "Run the given string as code", preprocessor = StrictParameterPreprocessor.class) - public Optional literalScript; + @Option(shortName = 'c', name = "code", parser = StrictOptionParser.class, description = "Run the given string as code") + public String literalScript; + + @Argument(description = "A file or URL to a Java code file") + public String scriptArg; - @CommandLine.Parameters(index = "1..*", arity = "0..*", description = "Parameters to pass on to the script") - public List userParams = new ArrayList<>(); + @Arguments(description = "Parameters for the script") + public List userParams; + + @Override + public void afterParse() { + super.afterParse(); + if (scriptArg != null) { + scriptMixin.scriptOrFile = scriptArg; + } + runMixin.resolveAfterParse(); + if (userParams == null) { + userParams = new ArrayList<>(); + } + } protected void requireScriptArgument() { - if (scriptMixin.scriptOrFile == null && ((runMixin.interactive != Boolean.TRUE && !literalScript.isPresent()) - || (literalScript.isPresent() && literalScript.get().isEmpty()))) { - throw new IllegalArgumentException("Missing required parameter: ''"); + if (scriptMixin.scriptOrFile == null && ((runMixin.interactive != Boolean.TRUE && literalScript == null) + || (literalScript != null && literalScript.isEmpty()))) { + throw new ExitException(EXIT_INVALID_INPUT, "Missing required parameter: ''"); } } private void rewriteScriptArguments() { - if (literalScript.isPresent() && !literalScript.get().isEmpty() + if (literalScript != null && !literalScript.isEmpty() && scriptMixin.scriptOrFile != null) { List args = new ArrayList<>(); args.add(scriptMixin.scriptOrFile); - args.addAll(userParams); + if (userParams != null) { + args.addAll(userParams); + } userParams = args; } } @@ -51,40 +69,39 @@ private void rewriteScriptArguments() { public Integer doCall() throws IOException { requireScriptArgument(); rewriteScriptArguments(); - userParams = handleRemoteFiles(userParams); - String scriptOrFile = scriptMixin.scriptOrFile; + String script = scriptMixin.scriptOrFile; ProjectBuilder pb = createProjectBuilderForRun(); Project prj; - if (literalScript.isPresent()) { - String script; - if (!literalScript.get().isEmpty()) { - script = literalScript.get(); + if (literalScript != null) { + String code; + if (!literalScript.isEmpty()) { + code = literalScript; } else { - script = scriptOrFile; + code = script; } - Util.verboseMsg("Literal Script to execute: '" + script + "'"); - prj = pb.build(LiteralScriptResourceResolver.stringToResourceRef(null, script, scriptMixin.forceType)); + Util.verboseMsg("Literal Script to execute: '" + code + "'"); + prj = pb.build(LiteralScriptResourceResolver.stringToResourceRef(null, code, scriptMixin.getForceType())); } else { - if (scriptOrFile != null) { - prj = pb.build(scriptOrFile); + if (script != null) { + prj = pb.build(script); } else { // HACK it's a crappy way to work around the fact that in the case of // interactive we might not have a file to reference but all the code // expects one to exist - prj = pb.build(LiteralScriptResourceResolver.stringToResourceRef(null, "", scriptMixin.forceType)); + prj = pb.build(LiteralScriptResourceResolver.stringToResourceRef(null, "", scriptMixin.getForceType())); } } if (Boolean.TRUE.equals(nativeMixin.nativeImage) - && (scriptMixin.forceType == Source.Type.jshell || prj.isJShell())) { + && (scriptMixin.getForceType() == Source.Type.jshell || prj.isJShell())) { warn(".jsh cannot be used with --native thus ignoring --native."); prj.setNativeImage(false); } - BuildContext ctx = BuildContext.forProject(prj, buildDir); + BuildContext ctx = BuildContext.forProject(prj, getBuildDir()); CmdGeneratorBuilder genb = Project.codeBuilder(ctx).build(); buildAgents(ctx); @@ -146,7 +163,7 @@ CmdGeneratorBuilder updateGeneratorForRun(CmdGeneratorBuilder gb) { .enableSystemAssertions(runMixin.enableSystemAssertions) .flightRecorderString(runMixin.flightRecorderString) .debugString(runMixin.debugString) - .classDataSharing(runMixin.cds); + .classDataSharing(runMixin.getCds()); return gb; } @@ -160,86 +177,4 @@ private static Map handleRemoteFiles(Map slots) slots.forEach((key, value) -> result.put(key, Util.substituteRemote(value))); return result; } - - /** - * Helper class to peek ahead at `--debug` to pickup --debug=5000, --debug 5000, - * --debug *:5000 as debug parameters but not --debug somefile.java - */ - static class DebugFallbackConsumer implements CommandLine.IParameterConsumer { - - final private static Pattern p = Pattern - .compile("(?
(.*?:)?(\\d+\\??))|(?\\S*)=(?\\S+\\??)"); - - @Override - public void consumeParameters(Stack args, CommandLine.Model.ArgSpec argSpec, - CommandLine.Model.CommandSpec commandSpec) { - String arg = args.peek(); - Matcher m = p.matcher(arg); - - if (!m.matches()) { - m = p.matcher(((CommandLine.Model.OptionSpec) argSpec).fallbackValue()); - } else { - args.pop(); - } - - if (m.matches()) { - Map kv = argSpec.getValue(); - - if (kv == null) { - kv = new LinkedHashMap<>(); - } - - String address = m.group("address"); - if (address != null) { - kv.put("address", address); - } else { - kv.put(m.group("key"), m.group("value")); - } - argSpec.setValue(kv); - } - } - } - - /** - * Helper class to peek ahead at `--jfr` to pickup x=y,t=y but not --jfr - * somefile.java - */ - static class KeyValueFallbackConsumer extends PatternFallbackConsumer { - private static final Pattern p = Pattern.compile("(\\S*?)=(\\S+)"); - - @Override - protected Pattern getValuePattern() { - return p; - } - - } - - static abstract class PatternFallbackConsumer implements CommandLine.IParameterConsumer { - - protected abstract Pattern getValuePattern(); - - @Override - public void consumeParameters(Stack args, CommandLine.Model.ArgSpec argSpec, - CommandLine.Model.CommandSpec commandSpec) { - Matcher m = getValuePattern().matcher(args.peek()); - if (m.matches()) { - argSpec.setValue(args.pop()); - } else { - String val, name; - if (argSpec.isOption()) { - CommandLine.Model.OptionSpec opt = (CommandLine.Model.OptionSpec) argSpec; - name = opt.longestName(); - val = opt.fallbackValue(); - } else { - name = argSpec.paramLabel(); - val = null; - } - try { - argSpec.setValue(val); - } catch (Exception badValue) { - throw new CommandLine.InitializationException("Value for " + name + " must be an string", badValue); - } - } - } - } } diff --git a/src/main/java/dev/jbang/cli/RunMixin.java b/src/main/java/dev/jbang/cli/RunMixin.java index e911b8737..afa51efdc 100644 --- a/src/main/java/dev/jbang/cli/RunMixin.java +++ b/src/main/java/dev/jbang/cli/RunMixin.java @@ -1,44 +1,91 @@ package dev.jbang.cli; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import picocli.CommandLine; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionList; + +import dev.jbang.Configuration; public class RunMixin { - @CommandLine.Option(names = { "-R", "--runtime-option", - "--java-options" }, description = "Options to pass to the Java runtime") + @OptionList(shortName = 'R', name = "runtime-option", aliases = { "java-options" }, description = "Options to pass to the Java runtime") public List javaRuntimeOptions; - @CommandLine.Option(names = { - "--jfr" }, fallbackValue = "${default.run.jfr}", parameterConsumer = Run.KeyValueFallbackConsumer.class, arity = "0..1", description = "Launch with Java Flight Recorder enabled.") + @Option(name = "jfr", parser = StrictOptionParser.class, description = "Launch with Java Flight Recorder enabled.") public String flightRecorderString; - @CommandLine.Option(names = { "-d", - "--debug" }, fallbackValue = "${default.run.debug}", parameterConsumer = Run.DebugFallbackConsumer.class, arity = "0..1", description = "Launch with java debug enabled. Set host/port or provide key/value list of JPDA options (default: ${FALLBACK-VALUE}) ") + @Option(shortName = 'd', name = "debug", parser = DebugOptionParser.class, description = "Launch with java debug enabled.") + public String debugRaw; + public Map debugString; - // should take arguments for package/classes when picocli fixes its flag - // handling bug in release 4.6. - // https://docs.oracle.com/cd/E19683-01/806-7930/assert-4/index.html - @CommandLine.Option(names = { "--enableassertions", "--ea" }, description = "Enable assertions") + @Option(name = "enableassertions", aliases = { "ea" }, hasValue = false, description = "Enable assertions") public Boolean enableAssertions; - @CommandLine.Option(names = { "--enablesystemassertions", "--esa" }, description = "Enable system assertions") + @Option(name = "enablesystemassertions", aliases = { "esa" }, hasValue = false, description = "Enable system assertions") public Boolean enableSystemAssertions; - @CommandLine.Option(names = { "--javaagent" }, parameterConsumer = KeyValueConsumer.class) + @OptionList(name = "javaagent") + List javaAgentRaw; + public Map javaAgentSlots; - @CommandLine.Option(names = { - "--cds" }, description = "If specified Class Data Sharing (CDS) will be used for building and running (requires Java 13+)", negatable = true) + @Option(name = "cds", hasValue = false, negatable = true, description = "If specified Class Data Sharing (CDS) will be used for building and running (requires Java 13+)") Boolean cds; - @CommandLine.Option(names = { "-i", "--interactive" }, description = "Activate interactive mode") + @Option(shortName = 'i', name = "interactive", hasValue = false, description = "Activate interactive mode") public Boolean interactive; + public void resolveAfterParse() { + Configuration cfg = Configuration.instance(); + if ("".equals(debugRaw)) { + String cfgDebug = cfg.get("run.debug"); + debugRaw = cfgDebug != null ? cfgDebug : "4004"; + } + if ("".equals(flightRecorderString)) { + String cfgJfr = cfg.get("run.jfr"); + flightRecorderString = cfgJfr != null ? cfgJfr : ""; + } + resolveDebugArgs(); + resolveJavaAgentSlots(); + } + + public void resolveDebugArgs() { + if (debugRaw != null && !debugRaw.isEmpty()) { + debugString = new LinkedHashMap<>(); + for (String part : debugRaw.split(",")) { + if (part.contains("=")) { + String[] kv = part.split("=", 2); + debugString.put(kv[0], kv[1]); + } else { + debugString.put("address", part); + } + } + } + } + + void resolveJavaAgentSlots() { + if (javaAgentRaw != null && !javaAgentRaw.isEmpty()) { + javaAgentSlots = new LinkedHashMap<>(); + for (String raw : javaAgentRaw) { + int eq = raw.indexOf('='); + if (eq >= 0) { + javaAgentSlots.put(raw.substring(0, eq), raw.substring(eq + 1)); + } else { + javaAgentSlots.put(raw, null); + } + } + } + } + + public Boolean getCds() { + return cds; + } + public List opts() { List opts = new ArrayList<>(); if (javaRuntimeOptions != null) { @@ -52,8 +99,6 @@ public List opts() { opts.add(flightRecorderString); } if (debugString != null) { - // TODO: this is not handling case of special characters in the values - // i.e. --debug=address=5000? or --debug=address=*:3333 for (Map.Entry e : debugString.entrySet()) { opts.add("-d"); opts.add(e.getKey() + "=" + e.getValue()); @@ -71,7 +116,7 @@ public List opts() { opts.add(e.getKey() + "=" + e.getValue()); } } - if (Boolean.TRUE.equals(cds)) { + if (Boolean.TRUE.equals(getCds())) { opts.add("--cds"); } if (Boolean.TRUE.equals(interactive)) { diff --git a/src/main/java/dev/jbang/cli/ScriptMixin.java b/src/main/java/dev/jbang/cli/ScriptMixin.java index ada9ca6a4..376bd3dba 100644 --- a/src/main/java/dev/jbang/cli/ScriptMixin.java +++ b/src/main/java/dev/jbang/cli/ScriptMixin.java @@ -1,41 +1,42 @@ package dev.jbang.cli; -import java.io.File; import java.util.ArrayList; import java.util.List; -import dev.jbang.source.Source; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionList; -import picocli.CommandLine; +import dev.jbang.source.Source; public class ScriptMixin { - @CommandLine.Option(names = { "-s", - "--sources" }, converter = CommaSeparatedConverter.class, description = "Add additional sources.") + + @OptionList(shortName = 's', name = "sources", valueSeparator = ',', description = "Add additional sources.") List sources; - @CommandLine.Option(names = { - "--files" }, converter = CommaSeparatedConverter.class, description = "Add additional files.") + @OptionList(name = "files", valueSeparator = ',', description = "Add additional files.") List resources; - @CommandLine.Option(names = { "-T", - "--source-type" }, description = "Force input to be interpreted as the given type. Can be: java, jshell, groovy, kotlin, or markdown") - Source.Type forceType; + @Option(shortName = 'T', name = "source-type", description = "Force input to be interpreted as the given type. Can be: java, jshell, groovy, kotlin, or markdown") + String forceTypeStr; - @CommandLine.Option(names = { - "--jsh" }, description = "Force input to be interpreted with jsh/jshell. Deprecated: use '--source-type jshell'") - void setForcejsh(boolean forceJsh) { - forceType = forceJsh ? Source.Type.jshell : null; - } + @Option(name = "jsh", hasValue = false, description = "Force input to be interpreted with jsh/jshell. Deprecated: use '--source-type jshell'") + Boolean forceJsh; - @CommandLine.Option(names = { "--catalog" }, description = "Path to catalog file to be used instead of the default") - File catalog; + @Option(name = "catalog", description = "Path to catalog file to be used instead of the default") + String catalog; - @CommandLine.Parameters(index = "0", arity = "0..1", description = "A reference to a source file") String scriptOrFile; + public Source.Type getForceType() { + if (Boolean.TRUE.equals(forceJsh)) { + return Source.Type.jshell; + } + return forceTypeStr != null ? Source.Type.valueOf(forceTypeStr) : null; + } + public void validate() { if (scriptOrFile == null) { - throw new IllegalArgumentException("Missing required parameter: ''"); + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, "Missing required parameter: ''"); } } @@ -59,13 +60,13 @@ public List opts() { opts.add(r); } } - if (forceType != null) { + if (getForceType() != null) { opts.add("--source-type"); - opts.add(forceType.toString()); + opts.add(getForceType().toString()); } if (catalog != null) { opts.add("--catalog"); - opts.add(catalog.getAbsolutePath()); + opts.add(catalog); } return opts; } diff --git a/src/main/java/dev/jbang/cli/StrictOptionParser.java b/src/main/java/dev/jbang/cli/StrictOptionParser.java new file mode 100644 index 000000000..43318025d --- /dev/null +++ b/src/main/java/dev/jbang/cli/StrictOptionParser.java @@ -0,0 +1,39 @@ +package dev.jbang.cli; + +import org.aesh.command.impl.internal.ProcessedOption; +import org.aesh.command.parser.OptionParser; +import org.aesh.command.parser.OptionParserException; +import org.aesh.parser.ParsedLineIterator; + +/** + * Option parser that only accepts values via = syntax. {@code --option=value} + * uses "value"; {@code --option} (without =) uses empty string as sentinel for + * "used without value". + */ +public class StrictOptionParser implements OptionParser { + + @Override + public void parse(ParsedLineIterator iter, ProcessedOption option) throws OptionParserException { + String word = iter.peekWord(); + + String prefix, optName; + if (option.isLongNameUsed()) { + prefix = "--"; + optName = option.name(); + } else { + prefix = "-"; + optName = option.shortName(); + } + + String fullPrefix = prefix + optName; + + if (word.startsWith(fullPrefix + "=")) { + String value = word.substring(fullPrefix.length() + 1); + option.addValue(value); + } else { + option.addValue(""); + } + + iter.pollParsedWord(); + } +} diff --git a/src/main/java/dev/jbang/cli/StrictParameterPreprocessor.java b/src/main/java/dev/jbang/cli/StrictParameterPreprocessor.java deleted file mode 100644 index 2d9f4e0b6..000000000 --- a/src/main/java/dev/jbang/cli/StrictParameterPreprocessor.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.jbang.cli; - -import java.util.Map; -import java.util.Stack; - -import picocli.CommandLine; - -/** - * preprocessor which strictly enforces you have to use a `=` to assign values. - * ie. only `--open=xyz` and `-o=xyz` will be accepted. Useful when you have - * option for which you like default value to be expressed without having it - * pick up additional values on the command line. i.e. `jbang edit --open - * myapp.java` should not treat `myapp.java` as editor to open but instead just - * open the default editor. - */ -public class StrictParameterPreprocessor implements CommandLine.IParameterPreprocessor { - - @Override - public boolean preprocess(Stack args, CommandLine.Model.CommandSpec commandSpec, - CommandLine.Model.ArgSpec argSpec, Map info) { - if (" ".equals(info.get("separator"))) { // parameter was not attached to option - // act as if the user specified fallback value - args.push(((CommandLine.Model.OptionSpec) argSpec).fallbackValue()); - } - return false; - } -} diff --git a/src/main/java/dev/jbang/cli/Template.java b/src/main/java/dev/jbang/cli/Template.java index c08d98d5a..49fe64ab1 100644 --- a/src/main/java/dev/jbang/cli/Template.java +++ b/src/main/java/dev/jbang/cli/Template.java @@ -2,6 +2,7 @@ import static dev.jbang.util.Util.entry; +import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; @@ -10,10 +11,17 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Argument; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Mixin; +import org.aesh.command.option.Option; +import org.aesh.command.option.OptionList; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import dev.jbang.Settings; import dev.jbang.catalog.Catalog; import dev.jbang.catalog.CatalogUtil; import dev.jbang.catalog.TemplateProperty; @@ -21,453 +29,388 @@ import dev.jbang.util.ConsoleOutput; import dev.jbang.util.Util; -import picocli.CommandLine; - -@CommandLine.Command(name = "template", description = "Manage templates for scripts.", subcommands = { - TemplateAdd.class, TemplateList.class, TemplateRemove.class }) -public class Template { +@GroupCommandDefinition(name = "template", description = "Manage templates for scripts.", groupCommands = { + Template.TemplateAdd.class, Template.TemplateList.class, Template.TemplateRemove.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class Template extends BaseCommand { public static final Pattern TPL_FILENAME_PATTERN = Pattern.compile("\\{filename}", Pattern.CASE_INSENSITIVE); public static final Pattern TPL_BASENAME_PATTERN = Pattern.compile("\\{basename}", Pattern.CASE_INSENSITIVE); - @CommandLine.Mixin - HelpMixin helpMixin; -} - -abstract class BaseTemplateCommand extends BaseCommand { - - @CommandLine.Option(names = { "--global", "-g" }, description = "Use the global (user) catalog file") - boolean global; + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'template'"); + return EXIT_INVALID_INPUT; + } - @CommandLine.Option(names = { "--file", "-f" }, description = "Path to the catalog file to use") - Path catalogFile; + static abstract class BaseTemplateCommand extends BaseCommand { - protected Path getCatalog(boolean strict) { - Path cat; - if (global) { - cat = Settings.getUserCatalogFile(); - } else { - if (catalogFile != null && Files.isDirectory(catalogFile)) { - Path defaultCatalog = catalogFile.resolve(Catalog.JBANG_CATALOG_JSON); - Path hiddenCatalog = catalogFile.resolve(Settings.JBANG_DOT_DIR).resolve(Catalog.JBANG_CATALOG_JSON); - if (!Files.exists(defaultCatalog) && Files.exists(hiddenCatalog)) { - cat = hiddenCatalog; - } else { - cat = defaultCatalog; - } - } else { - cat = catalogFile; - } - if (strict && cat != null && !Files.isRegularFile(cat)) { - throw new IllegalArgumentException("Catalog file not found at: " + catalogFile); - } - } - return cat; + @Mixin + CatalogFileOptionsMixin catalogOptions; } -} -@CommandLine.Command(name = "add", description = "Add template for script reference.") -class TemplateAdd extends BaseTemplateCommand { + @CommandDefinition(name = "add", description = "Add template for script reference.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TemplateAdd extends BaseTemplateCommand { - @CommandLine.Option(names = { "--description", - "-d" }, description = "A description for the template") - String description; + @Option(shortName = 'd', name = "description", description = "A description for the template") + String description; - @CommandLine.Option(names = { "--name" }, description = "A name for the template") - String name; + @Option(name = "name", description = "A name for the template") + String name; - @CommandLine.Option(names = { - "--force" }, description = "Force overwriting of existing template") - boolean force; + @Option(name = "force", hasValue = false, description = "Force overwriting of existing template") + boolean force; - @CommandLine.Parameters(paramLabel = "files", index = "0..*", arity = "1..*", description = "Paths or URLs to template files") - List fileRefs; + @Arguments(description = "Paths or URLs to template files", required = true) + List fileRefs; - @CommandLine.Option(names = { "--property", - "-P" }, description = "Template property", converter = TemplatePropertyConverter.class) - List properties; + @OptionList(shortName = 'P', name = "property", description = "Template property (key=description::defaultValue)") + List propertyStrings; - @Override - public Integer doCall() { - if (name != null && !Catalog.isValidName(name)) { - throw new IllegalArgumentException( - "Invalid template name, it should start with a letter followed by 0 or more letters, digits, underscores or hyphens"); - } + @Override + public Integer doCall() throws IOException { + if (name != null && !Catalog.isValidName(name)) { + throw new ExitException(EXIT_INVALID_INPUT, + "Invalid template name, it should start with a letter followed by 0 or more letters, digits, underscores or hyphens"); + } - List> splitRefs = fileRefs - .stream() - // Turn list of files into a list of target=source pairs - .map(TemplateAdd::splitFileRef) - // Check that the source files/URLs exist - .filter(TemplateAdd::refExists) - .collect(Collectors.toList()); - - // Make sure we have at least a single {basename} or {filename} target - boolean hasTargetPattern = false; - for (Map.Entry splitRef : splitRefs) { - String target = splitRef.getKey(); - if (target != null && (Template.TPL_FILENAME_PATTERN.matcher(target).find() - || Template.TPL_BASENAME_PATTERN.matcher(target).find())) { - hasTargetPattern = true; + List> splitRefs = fileRefs + .stream() + // Turn list of files into a list of target=source pairs + .map(TemplateAdd::splitFileRef) + // Check that the source files/URLs exist + .filter(TemplateAdd::refExists) + .collect(Collectors.toList()); + + // Make sure we have at least a single {basename} or {filename} target + boolean hasTargetPattern = false; + for (Map.Entry splitRef : splitRefs) { + String target = splitRef.getKey(); + if (target != null && (Template.TPL_FILENAME_PATTERN.matcher(target).find() + || Template.TPL_BASENAME_PATTERN.matcher(target).find())) { + hasTargetPattern = true; + } } - } - if (!hasTargetPattern) { - // There's no {basename} or {filename} in any of the targets - Map.Entry firstRef = splitRefs.get(0); - if (firstRef.getKey() == null) { - String name = firstRef.getValue(); - String ext = name.endsWith(".qute") ? Util.extension(Util.base(name)) : Util.extension(name); - String target = ext.isEmpty() ? "{filename}" : "{basename}." + ext; - splitRefs.set(0, entry(target, firstRef.getValue())); - warn("No explicit target pattern was set, using first file: " + target + "=" + firstRef.getValue()); - } else { - throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, - "A target pattern is required. Prefix at least one of the files with '{filename}=' or '{basename}.ext='"); + if (!hasTargetPattern) { + // There's no {basename} or {filename} in any of the targets + Map.Entry firstRef = splitRefs.get(0); + if (firstRef.getKey() == null) { + String refName = firstRef.getValue(); + String ext = refName.endsWith(".qute") ? Util.extension(Util.base(refName)) + : Util.extension(refName); + String target = ext.isEmpty() ? "{filename}" : "{basename}." + ext; + splitRefs.set(0, entry(target, firstRef.getValue())); + warn("No explicit target pattern was set, using first file: " + target + "=" + firstRef.getValue()); + } else { + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, + "A target pattern is required. Prefix at least one of the files with '{filename}=' or '{basename}.ext='"); + } } - } - Map fileRefsMap = new TreeMap<>(); - splitRefs - .stream() - // Make sure all file refs have a target - .map(TemplateAdd::ensureTarget) - // Create map of file refs - .forEach(splitRef -> fileRefsMap.put(splitRef.getKey(), splitRef.getValue())); + Map fileRefsMap = new TreeMap<>(); + splitRefs + .stream() + // Make sure all file refs have a target + .map(TemplateAdd::ensureTarget) + // Create map of file refs + .forEach(splitRef -> fileRefsMap.put(splitRef.getKey(), splitRef.getValue())); - if (name == null) { - name = CatalogUtil.nameFromRef(fileRefs.get(0)); - } + if (name == null) { + name = CatalogUtil.nameFromRef(fileRefs.get(0)); + } - Map propertiesMap = Optional.ofNullable(properties) - .map(Collection::stream) - .map(stream -> stream.collect(Collectors.toMap( - TemplatePropertyInput::getKey, - (TemplatePropertyInput templatePropertyInput) -> new TemplateProperty( - templatePropertyInput.getDescription(), - templatePropertyInput.getDefaultValue())))) - .orElse(new HashMap<>()); - - Path catFile = getCatalog(false); - if (catFile == null) { - catFile = Catalog.getCatalogFile(null); - } - if (force || !CatalogUtil.hasTemplate(catFile, name)) { - CatalogUtil.addTemplate(catFile, name, fileRefsMap, description, propertiesMap); - } else { - Util.infoMsg("A template with name '" + name + "' already exists, use '--force' to add anyway."); - return EXIT_INVALID_INPUT; + Map propertiesMap = parseProperties(propertyStrings); + + Path catFile = catalogOptions.getCatalogOrDefault(); + if (force || !CatalogUtil.hasTemplate(catFile, name)) { + CatalogUtil.addTemplate(catFile, name, fileRefsMap, description, propertiesMap); + } else { + Util.infoMsg("A template with name '" + name + "' already exists, use '--force' to add anyway."); + return EXIT_INVALID_INPUT; + } + info(String.format("Template '%s' added to '%s'", name, catFile)); + return EXIT_OK; } - info(String.format("Template '%s' added to '%s'", name, catFile)); - return EXIT_OK; - } - private static Map.Entry splitFileRef(String fileRef) { - String[] ref = fileRef.split("=", 2); - String target; - String source; - if (ref.length == 2) { - source = ref[1]; - target = ref[0]; - Path t = Paths.get(target).normalize(); - if (t.isAbsolute()) { - throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, - "Target name may not be absolute: '" + target + "'"); + static Map parseProperties(List propStrings) { + if (propStrings == null || propStrings.isEmpty()) { + return new HashMap<>(); } - if (t.normalize().startsWith("..")) { - throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, - "Target may not refer to parent folders: '" + target + "'"); + Map result = new HashMap<>(); + for (String prop : propStrings) { + String key; + String desc = null; + String defVal = null; + int eqIdx = prop.indexOf('='); + if (eqIdx >= 0) { + key = prop.substring(0, eqIdx); + String rest = prop.substring(eqIdx + 1); + int sepIdx = rest.indexOf("::"); + if (sepIdx >= 0) { + desc = rest.substring(0, sepIdx); + defVal = rest.substring(sepIdx + 2); + } else { + desc = rest; + } + } else { + String[] parts = prop.split(":", 3); + key = parts[0]; + if (parts.length > 1) { + desc = parts[1]; + } + if (parts.length > 2) { + defVal = parts[2]; + } + } + if (desc != null && desc.isEmpty()) { + desc = null; + } + if (defVal != null && defVal.isEmpty()) { + defVal = null; + } + result.put(key, new TemplateProperty(desc, defVal)); } - } else { - source = ref[0]; - target = null; + return result; } - return entry(target, source); - } - private static boolean refExists(Map.Entry splitRef) { - String source = splitRef.getValue(); - ResourceRef resourceRef = ResourceRef.forResource(source); - if (resourceRef == null || !Files.isReadable(resourceRef.getFile())) { - throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, - "File could not be found or read: '" + source + "'"); + private static Map.Entry splitFileRef(String fileRef) { + String[] ref = fileRef.split("=", 2); + String target; + String source; + if (ref.length == 2) { + source = ref[1]; + target = ref[0]; + Path t = Paths.get(target).normalize(); + if (t.isAbsolute()) { + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, + "Target name may not be absolute: '" + target + "'"); + } + if (t.normalize().startsWith("..")) { + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, + "Target may not refer to parent folders: '" + target + "'"); + } + } else { + source = ref[0]; + target = null; + } + return entry(target, source); } - return true; - } - private static Map.Entry ensureTarget(Map.Entry splitRef) { - if (splitRef.getKey() == null) { + private static boolean refExists(Map.Entry splitRef) { String source = splitRef.getValue(); - Path t = Paths.get(source).normalize(); - String target = t.getFileName().toString(); - if (target.endsWith(".qute")) { - target = target.substring(0, target.length() - 5); + ResourceRef resourceRef = ResourceRef.forResource(source); + if (resourceRef == null || !Files.isReadable(resourceRef.getFile())) { + throw new ExitException(BaseCommand.EXIT_INVALID_INPUT, + "File could not be found or read: '" + source + "'"); } - return entry(target, source); - } else { - return splitRef; + return true; } - } - public static class TemplatePropertyInput { - private String key; - private String description; - private String defaultValue; - - public TemplatePropertyInput() { + private static Map.Entry ensureTarget(Map.Entry splitRef) { + if (splitRef.getKey() == null) { + String source = splitRef.getValue(); + Path t = Paths.get(source).normalize(); + String target = t.getFileName().toString(); + if (target.endsWith(".qute")) { + target = target.substring(0, target.length() - 5); + } + return entry(target, source); + } else { + return splitRef; + } } + } - public TemplatePropertyInput(String key, String description, String defaultValue) { - this.key = key; - this.description = description; - this.defaultValue = defaultValue; - } + @CommandDefinition(name = "list", description = "Lists locally defined templates or from the given catalog.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TemplateList extends BaseTemplateCommand { - public String getKey() { - return key; - } + @Option(name = "show-origin", hasValue = false, description = "Show the origin of the template") + boolean showOrigin; - public void setKey(String key) { - this.key = key; - } + @Option(name = "show-files", hasValue = false, description = "Show list of files for each template") + boolean showFiles; - public String getDescription() { - return description; - } + @Option(name = "show-properties", hasValue = false, description = "Show list of properties for each template") + boolean showProperties; - public void setDescription(String description) { - this.description = description; - } + @Argument(description = "The name of a catalog") + String catalogName; - public String getDefaultValue() { - return defaultValue; - } + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } + private static final int INDENT_SIZE = 3; @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - TemplatePropertyInput that = (TemplatePropertyInput) o; - - if (key != null ? !key.equals(that.key) : that.key != null) - return false; - if (description != null ? !description.equals(that.description) : that.description != null) - return false; - return defaultValue != null ? defaultValue.equals(that.defaultValue) : that.defaultValue == null; + public Integer doCall() throws IOException { + PrintStream out = System.out; + Catalog catalog; + Path cat = catalogOptions.getCatalog(true); + if (catalogName != null) { + catalog = Catalog.getByName(catalogName); + } else if (cat != null) { + catalog = Catalog.get(cat); + } else { + catalog = Catalog.getMerged(true, false); + } + if (showOrigin) { + printTemplatesWithOrigin(out, catalogName, catalog, showFiles, showProperties, format); + } else { + printTemplates(out, catalogName, catalog, showFiles, showProperties, format); + } + return EXIT_OK; } - @Override - public int hashCode() { - int result = key != null ? key.hashCode() : 0; - result = 31 * result + (description != null ? description.hashCode() : 0); - result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0); - return result; + static void printTemplates(PrintStream out, String catalogName, Catalog catalog, boolean showFiles, + boolean showProperties, String format) { + List templates = catalog.templates + .keySet() + .stream() + .sorted() + .map(name -> getTemplateOut(catalogName, catalog, name, + showFiles, showProperties)) + .collect(Collectors.toList()); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(templates, out); + } else { + templates.forEach(t -> printTemplate(out, t, 0)); + } } - @Override - public String toString() { - return "TemplatePropertyInput{" + - "key='" + key + '\'' + - ", description='" + description + '\'' + - ", defaultValue='" + defaultValue + '\'' + - '}'; + static List getTemplatesWithOrigin(String catalogName, + dev.jbang.catalog.Catalog catalog, boolean showFiles, + boolean showProperties) { + Map> groups = catalog.templates + .keySet() + .stream() + .sorted() + .map(name -> getTemplateOut(catalogName, + catalog, name, showFiles, + showProperties)) + .collect(Collectors.groupingBy( + t -> t._catalogRef)); + return groups.entrySet() + .stream() + .map(e -> new dev.jbang.cli.Catalog.CatalogList.CatalogOut(null, e.getKey(), + null, e.getValue(), null)) + .collect(Collectors.toList()); } - } -} - -@CommandLine.Command(name = "list", description = "Lists locally defined templates or from the given catalog.") -class TemplateList extends BaseTemplateCommand { - - @CommandLine.Option(names = { "--show-origin" }, description = "Show the origin of the template") - boolean showOrigin; - - @CommandLine.Option(names = { "--show-files" }, description = "Show list of files for each template") - boolean showFiles; - - @CommandLine.Option(names = { "--show-properties" }, description = "Show list of properties for each template") - boolean showProperties; - - @CommandLine.Parameters(paramLabel = "catalogName", index = "0", description = "The name of a catalog", arity = "0..1") - String catalogName; - - @CommandLine.Mixin - FormatMixin formatMixin; - private static final int INDENT_SIZE = 3; - - @Override - public Integer doCall() { - PrintStream out = System.out; - Catalog catalog; - Path cat = getCatalog(true); - if (catalogName != null) { - catalog = Catalog.getByName(catalogName); - } else if (cat != null) { - catalog = Catalog.get(cat); - } else { - catalog = Catalog.getMerged(true, false); - } - if (showOrigin) { - printTemplatesWithOrigin(out, catalogName, catalog, showFiles, showProperties, formatMixin.format); - } else { - printTemplates(out, catalogName, catalog, showFiles, showProperties, formatMixin.format); + static void printTemplatesWithOrigin(PrintStream out, String catalogName, dev.jbang.catalog.Catalog catalog, + boolean showFiles, + boolean showProperties, String format) { + List catalogs = getTemplatesWithOrigin(catalogName, catalog, + showFiles, showProperties); + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(catalogs, out); + } else { + catalogs.forEach(cat -> { + out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); + cat.templates.forEach(t -> printTemplate(out, t, 1)); + }); + } } - return EXIT_OK; - } - static void printTemplates(PrintStream out, String catalogName, Catalog catalog, boolean showFiles, - boolean showProperties, FormatMixin.Format format) { - List templates = catalog.templates - .keySet() - .stream() - .sorted() - .map(name -> getTemplateOut(catalogName, catalog, name, - showFiles, showProperties)) - .collect(Collectors.toList()); - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(templates, out); - } else { - templates.forEach(t -> printTemplate(out, t, 0)); + static class TemplateOut { + public String name; + public String catalogName; + public String fullName; + public String description; + public List fileRefs; + public Map properties; + public transient ResourceRef _catalogRef; } - } - static List getTemplatesWithOrigin(String catalogName, Catalog catalog, boolean showFiles, - boolean showProperties) { - Map> groups = catalog.templates - .keySet() - .stream() - .sorted() - .map(name -> getTemplateOut(catalogName, - catalog, name, showFiles, - showProperties)) - .collect(Collectors.groupingBy( - t -> t._catalogRef)); - return groups.entrySet() - .stream() - .map(e -> new CatalogList.CatalogOut(null, e.getKey(), - null, e.getValue(), null)) - .collect(Collectors.toList()); - } - - static void printTemplatesWithOrigin(PrintStream out, String catalogName, Catalog catalog, boolean showFiles, - boolean showProperties, FormatMixin.Format format) { - List catalogs = getTemplatesWithOrigin(catalogName, catalog, showFiles, showProperties); - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(catalogs, out); - } else { - catalogs.forEach(cat -> { - out.println(ConsoleOutput.bold(dev.jbang.catalog.Catalog.simplifyRef(cat.resourceRef))); - cat.templates.forEach(t -> printTemplate(out, t, 1)); - }); + static class FileRefOut { + String source; + String resolved; + String destination; } - } - static class TemplateOut { - public String name; - public String catalogName; - public String fullName; - public String description; - public List fileRefs; - public Map properties; - public transient ResourceRef _catalogRef; - } - - static class FileRefOut { - String source; - String resolved; - String destination; - } - - private static TemplateOut getTemplateOut(String catalogName, Catalog catalog, String name, - boolean showFiles, boolean showProperties) { - dev.jbang.catalog.Template template = catalog.templates.get(name); - String catName = catalogName != null ? dev.jbang.catalog.Catalog.simplifyRef(catalogName) - : CatalogUtil.catalogRef(name); - String fullName = catalogName != null ? name + "@" + catName : name; - - TemplateOut out = new TemplateOut(); - out.name = name; - out.catalogName = catName; - out.fullName = fullName; - out.description = template.description; - if (showFiles && template.fileRefs != null) { - out.fileRefs = new ArrayList<>(); - for (String dest : template.fileRefs.keySet()) { - String ref = template.fileRefs.get(dest); - if (ref == null || ref.isEmpty()) { - ref = dest; + private static TemplateOut getTemplateOut(String catalogName, dev.jbang.catalog.Catalog catalog, String name, + boolean showFiles, boolean showProperties) { + dev.jbang.catalog.Template template = catalog.templates.get(name); + String catName = catalogName != null ? dev.jbang.catalog.Catalog.simplifyRef(catalogName) + : CatalogUtil.catalogRef(name); + String fullName = catalogName != null ? name + "@" + catName : name; + + TemplateOut out = new TemplateOut(); + out.name = name; + out.catalogName = catName; + out.fullName = fullName; + out.description = template.description; + if (showFiles && template.fileRefs != null) { + out.fileRefs = new ArrayList<>(); + for (String dest : template.fileRefs.keySet()) { + String ref = template.fileRefs.get(dest); + if (ref == null || ref.isEmpty()) { + ref = dest; + } + FileRefOut fro = new FileRefOut(); + fro.source = ref; + fro.destination = dest; + fro.resolved = template.resolve(ref); + out.fileRefs.add(fro); } - FileRefOut fro = new FileRefOut(); - fro.source = ref; - fro.destination = dest; - fro.resolved = template.resolve(ref); - out.fileRefs.add(fro); } + out.properties = showProperties ? template.properties : null; + out._catalogRef = template.catalog.catalogRef; + return out; } - out.properties = showProperties ? template.properties : null; - out._catalogRef = template.catalog.catalogRef; - return out; - } - private static void printTemplate(PrintStream out, TemplateOut template, int indent) { - String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); - String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); - String prefix3 = Util.repeat(" ", (indent + 2) * INDENT_SIZE); - out.print(Util.repeat(" ", indent)); - out.println(prefix1 + dev.jbang.cli.CatalogList.getColoredFullName(template.fullName)); - if (template.description != null) { - out.println(prefix2 + template.description); - } - if (template.fileRefs != null) { - out.println(prefix2 + "Files:"); - for (FileRefOut fro : template.fileRefs) { - if (fro.resolved.equals(fro.destination)) { - out.println(prefix3 + fro.resolved); - } else { - out.println(prefix3 + fro.destination + " (from " + fro.resolved + ")"); - } + private static void printTemplate(PrintStream out, TemplateOut template, int indent) { + String prefix1 = Util.repeat(" ", indent * INDENT_SIZE); + String prefix2 = Util.repeat(" ", (indent + 1) * INDENT_SIZE); + String prefix3 = Util.repeat(" ", (indent + 2) * INDENT_SIZE); + out.print(Util.repeat(" ", indent)); + out.println(prefix1 + dev.jbang.cli.Catalog.CatalogList.getColoredFullName(template.fullName)); + if (template.description != null) { + out.println(prefix2 + template.description); } - } - if (template.properties != null) { - out.println(prefix2 + "Properties:"); - for (Map.Entry entry : template.properties.entrySet()) { - StringBuilder propertyLineBuilder = new StringBuilder() - .append(prefix3) - .append(ConsoleOutput.cyan(entry.getKey())) - .append(" = "); - if (entry.getValue().getDescription() != null) { - propertyLineBuilder.append(entry.getValue().getDescription()).append(" "); + if (template.fileRefs != null) { + out.println(prefix2 + "Files:"); + for (FileRefOut fro : template.fileRefs) { + if (fro.resolved.equals(fro.destination)) { + out.println(prefix3 + fro.resolved); + } else { + out.println(prefix3 + fro.destination + " (from " + fro.resolved + ")"); + } } - if (entry.getValue().getDefaultValue() != null) { - propertyLineBuilder.append("[").append(entry.getValue().getDefaultValue()).append("]"); + } + if (template.properties != null) { + out.println(prefix2 + "Properties:"); + for (Map.Entry entry : template.properties.entrySet()) { + StringBuilder propertyLineBuilder = new StringBuilder() + .append(prefix3) + .append(ConsoleOutput.cyan(entry.getKey())) + .append(" = "); + if (entry.getValue().getDescription() != null) { + propertyLineBuilder.append(entry.getValue().getDescription()).append(" "); + } + if (entry.getValue().getDefaultValue() != null) { + propertyLineBuilder.append("[").append(entry.getValue().getDefaultValue()).append("]"); + } + out.println(propertyLineBuilder); } - out.println(propertyLineBuilder); } } } -} -@CommandLine.Command(name = "remove", description = "Remove existing template.") -class TemplateRemove extends BaseTemplateCommand { + @CommandDefinition(name = "remove", description = "Remove existing template.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TemplateRemove extends BaseTemplateCommand { - @CommandLine.Parameters(paramLabel = "name", index = "0", description = "The name of the template", arity = "1") - String name; + @Argument(description = "The name of the template", required = true) + String name; - @Override - public Integer doCall() { - final Path cat = getCatalog(true); - if (cat != null) { - CatalogUtil.removeTemplate(cat, name); - } else { - CatalogUtil.removeNearestTemplate(name); + @Override + public Integer doCall() throws IOException { + final Path cat = catalogOptions.getCatalog(true); + if (cat != null) { + CatalogUtil.removeTemplate(cat, name); + } else { + CatalogUtil.removeNearestTemplate(name); + } + return EXIT_OK; } - return EXIT_OK; } } diff --git a/src/main/java/dev/jbang/cli/TemplatePropertyConverter.java b/src/main/java/dev/jbang/cli/TemplatePropertyConverter.java deleted file mode 100644 index 8b2e856da..000000000 --- a/src/main/java/dev/jbang/cli/TemplatePropertyConverter.java +++ /dev/null @@ -1,42 +0,0 @@ -package dev.jbang.cli; - -import org.apache.commons.lang3.StringUtils; - -import picocli.CommandLine.ITypeConverter; - -/** - * Template property converter that is able to parse the following input: - * - *
    - *
  • property-name
  • - *
  • property-name:property-description
  • - *
  • property-name:property-description:default-value
  • - *
  • property-name::default-value
  • - *
- * - * Examples: - *
    - *
  • test-key
  • - *
  • "test-key:This is a description for the property"
  • - *
  • "test-key:This is a description for the property:2.11"
  • - *
  • "test-key::2.11"
  • - *
- * - */ -public class TemplatePropertyConverter implements ITypeConverter { - @Override - public TemplateAdd.TemplatePropertyInput convert(final String input) throws Exception { - String[] propertyParts = input.split(":"); - TemplateAdd.TemplatePropertyInput templatePropertyInput = new TemplateAdd.TemplatePropertyInput(); - if (propertyParts.length > 0 && StringUtils.isNotBlank(propertyParts[0])) { - templatePropertyInput.setKey(propertyParts[0]); - } - if (propertyParts.length > 1 && StringUtils.isNotBlank(propertyParts[1])) { - templatePropertyInput.setDescription(propertyParts[1]); - } - if (propertyParts.length > 2 && StringUtils.isNotBlank(propertyParts[2])) { - templatePropertyInput.setDefaultValue(propertyParts[2]); - } - return templatePropertyInput; - } -} diff --git a/src/main/java/dev/jbang/cli/Trust.java b/src/main/java/dev/jbang/cli/Trust.java index 128ff860b..9338b05a7 100644 --- a/src/main/java/dev/jbang/cli/Trust.java +++ b/src/main/java/dev/jbang/cli/Trust.java @@ -2,71 +2,93 @@ import static dev.jbang.cli.BaseCommand.EXIT_OK; +import java.io.IOException; import java.io.PrintStream; import java.util.List; import java.util.stream.Collectors; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Arguments; +import org.aesh.command.option.Option; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import dev.jbang.Settings; import dev.jbang.net.TrustedSources; -import picocli.CommandLine; +@GroupCommandDefinition(name = "trust", description = "Manage which domains you trust to run scripts from.", groupCommands = { + Trust.TrustAdd.class, Trust.TrustList.class, Trust.TrustRemove.class }, generateHelp = true, helpGroup = "Configuration", defaultValueProvider = JBangDefaultValueProvider.class) +public class Trust extends BaseCommand { -@CommandLine.Command(name = "trust", description = "Manage which domains you trust to run scripts from.") -public class Trust { + @Override + public Integer doCall() throws IOException { + System.err.println("Missing required subcommand for 'trust'"); + return EXIT_INVALID_INPUT; + } - @CommandLine.Mixin - HelpMixin helpMixin; + @CommandDefinition(name = "add", description = "Add trust domains.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TrustAdd extends BaseCommand { - @CommandLine.Spec - CommandLine.Model.CommandSpec spec; + @Arguments(description = "Rules for trusted sources", required = true) + List rules; - @CommandLine.Command(name = "add", description = "Add trust domains.") - public Integer add( - @CommandLine.Parameters(index = "0", description = "Rules for trusted sources", arity = "1..*") List rules) { - TrustedSources.instance().add(rules); - return EXIT_OK; + @Override + public Integer doCall() throws IOException { + TrustedSources.instance().add(rules); + return EXIT_OK; + } } - @CommandLine.Command(name = "list", description = "Show defined trust domains.") - public Integer list( - @CommandLine.Option(names = { - "--format" }, description = "Specify output format ('text' or 'json')") FormatMixin.Format format) { - int idx = 0; - PrintStream out = System.out; - if (format == FormatMixin.Format.json) { - Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); - parser.toJson(TrustedSources.instance().getTrustedSources(), out); - } else { - for (String src : TrustedSources.instance().getTrustedSources()) { - out.println(++idx + " = " + src); + @CommandDefinition(name = "list", description = "Show defined trust domains.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TrustList extends BaseCommand { + + @Option(name = "format", description = "Specify output format ('text' or 'json')") + String format; + + @Override + public Integer doCall() throws IOException { + int idx = 0; + PrintStream out = System.out; + if ("json".equals(format)) { + Gson parser = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + parser.toJson(TrustedSources.instance().getTrustedSources(), out); + } else { + for (String src : TrustedSources.instance().getTrustedSources()) { + out.println(++idx + " = " + src); + } } + return EXIT_OK; } - return EXIT_OK; } - @CommandLine.Command(name = "remove", description = "Remove trust domains.") - public Integer remove( - @CommandLine.Parameters(index = "0", description = "Rules for trusted sources", arity = "1..*") List rules) { - List newrules = rules.stream() - .map(this::toDomain) - .collect(Collectors.toList()); - TrustedSources.instance().remove(newrules, Settings.getTrustedSourcesFile().toFile()); - return EXIT_OK; - } + @CommandDefinition(name = "remove", description = "Remove trust domains.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class TrustRemove extends BaseCommand { + + @Arguments(description = "Rules for trusted sources", required = true) + List rules; + + @Override + public Integer doCall() throws IOException { + List newrules = rules.stream() + .map(this::toDomain) + .collect(Collectors.toList()); + TrustedSources.instance().remove(newrules, Settings.getTrustedSourcesFile().toFile()); + return EXIT_OK; + } - private String toDomain(String src) { - String[] sources = TrustedSources.instance().getTrustedSources(); - try { - int idx = Integer.parseInt(src) - 1; - if (idx >= 0 && idx < sources.length) { - return sources[idx]; + private String toDomain(String src) { + String[] sources = TrustedSources.instance().getTrustedSources(); + try { + int idx = Integer.parseInt(src) - 1; + if (idx >= 0 && idx < sources.length) { + return sources[idx]; + } + } catch (NumberFormatException e) { + // Ignore } - } catch (NumberFormatException e) { - // Ignore + return src; } - return src; } -} \ No newline at end of file +} diff --git a/src/main/java/dev/jbang/cli/Version.java b/src/main/java/dev/jbang/cli/Version.java index c400b5eb6..2960bde9a 100644 --- a/src/main/java/dev/jbang/cli/Version.java +++ b/src/main/java/dev/jbang/cli/Version.java @@ -1,7 +1,9 @@ package dev.jbang.cli; import java.io.IOException; -import java.io.PrintWriter; + +import org.aesh.command.CommandDefinition; +import org.aesh.command.option.Option; import dev.jbang.Settings; import dev.jbang.dependencies.ArtifactResolver; @@ -9,15 +11,13 @@ import dev.jbang.util.Util; import dev.jbang.util.VersionChecker; -import picocli.CommandLine; - -@CommandLine.Command(name = "version", description = "Display version info.") +@CommandDefinition(name = "version", description = "Display version info.", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) public class Version extends BaseCommand { - @CommandLine.Option(names = { "--check" }, description = "Check if a new version of jbang is available") + @Option(name = "check", hasValue = false, description = "Check if a new version of jbang is available") boolean checkForUpdate; - @CommandLine.Option(names = { "--update" }, description = "Update jbang to the latest version") + @Option(name = "update", hasValue = false, description = "Update jbang to the latest version") boolean update; @Override @@ -25,7 +25,7 @@ public Integer doCall() { if (update) { if (VersionChecker.updateOrInform(checkForUpdate)) { try { - AppInstall.installJBang(true); + App.AppInstall.installJBang(true); } catch (IOException e) { throw new ExitException(EXIT_INTERNAL_ERROR, "Could not install command", e); } @@ -38,15 +38,15 @@ public Integer doCall() { } if (isVerbose()) { - PrintWriter out = spec.commandLine().getOut(); - out.println("Cache: " + Settings.getCacheDir()); - out.println("Config: " + Settings.getConfigDir()); - out.println("Repository: " + ArtifactResolver.getLocalMavenRepo()); - out.println("Java: " + System.getProperty("java.home") + " [" + System.getProperty("java.version") + "]"); - out.println("OS: " + Util.getOS()); - out.println("Arch: " + Util.getArch()); - out.println("Shell: " + Util.getShell()); - out.println("Native Image: " + JavaUtil.inNativeImage()); + System.err.println("Cache: " + Settings.getCacheDir()); + System.err.println("Config: " + Settings.getConfigDir()); + System.err.println("Repository: " + ArtifactResolver.getLocalMavenRepo()); + System.err + .println("Java: " + System.getProperty("java.home") + " [" + System.getProperty("java.version") + "]"); + System.err.println("OS: " + Util.getOS()); + System.err.println("Arch: " + Util.getArch()); + System.err.println("Shell: " + Util.getShell()); + System.err.println("Native Image: " + JavaUtil.inNativeImage()); } return EXIT_OK; diff --git a/src/main/java/dev/jbang/cli/VersionProvider.java b/src/main/java/dev/jbang/cli/VersionProvider.java deleted file mode 100644 index 934d0f16a..000000000 --- a/src/main/java/dev/jbang/cli/VersionProvider.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.jbang.cli; - -import dev.jbang.util.Util; - -import picocli.CommandLine; - -public class VersionProvider implements CommandLine.IVersionProvider { - @Override - public String[] getVersion() throws Exception { - return new String[] { Util.getJBangVersion() }; - } -} diff --git a/src/main/java/dev/jbang/cli/Wrapper.java b/src/main/java/dev/jbang/cli/Wrapper.java index fab782836..e16e9b7ce 100644 --- a/src/main/java/dev/jbang/cli/Wrapper.java +++ b/src/main/java/dev/jbang/cli/Wrapper.java @@ -7,79 +7,93 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.List; -import dev.jbang.util.Util; +import org.aesh.command.CommandDefinition; +import org.aesh.command.GroupCommandDefinition; +import org.aesh.command.option.Option; -import picocli.CommandLine; +import dev.jbang.util.Util; -@CommandLine.Command(name = "wrapper", description = "Manage jbang wrapper for a folder.") -public class Wrapper { +@GroupCommandDefinition(name = "wrapper", description = "Manage jbang wrapper for a folder.", groupCommands = { + Wrapper.WrapperInstall.class }, generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) +public class Wrapper extends BaseCommand { public static final String DIR_NAME = ".jbang"; public static final String JAR_NAME = "jbang.jar"; public static final List SCRIPT_NAMES = Arrays.asList("jbang", "jbang.cmd", "jbang.ps1"); - @CommandLine.Mixin - HelpMixin helpMixin; + @Override + public Integer doCall() { + System.err.println("Missing required subcommand for 'wrapper'"); + return EXIT_INVALID_INPUT; + } - @CommandLine.Command(name = "install", description = "Install/Setup jbang as a `wrapper` script in a folder") - public Integer install( - @CommandLine.Option(names = { "-d", - "--dir" }, description = "The folder to install the wrapper into.") Path dest, - @CommandLine.Option(names = { "-f", - "--force" }, description = "Force installation of wrapper even if files already exist") boolean force) { - if (!Files.isDirectory(dest)) { - throw new ExitException(EXIT_INVALID_INPUT, "Destination folder does not exist"); - } - if ((checkScripts(dest) || checkJar(dest.resolve(DIR_NAME))) && !force) { - Util.warnMsg("Wrapper already exists. Use --force to install anyway"); - return EXIT_OK; - } - try { - Path jar = Util.getJarLocation(); - String exeName = "jbang.bin"; - if (Util.isWindows()) { - exeName = "jbang.bin.exe"; + @CommandDefinition(name = "install", description = "Install/Setup jbang as a wrapper script in a folder", generateHelp = true, defaultValueProvider = JBangDefaultValueProvider.class) + public static class WrapperInstall extends BaseCommand { + + @Option(shortName = 'd', name = "dir", description = "The folder to install the wrapper into.") + String dest; + + @Option(shortName = 'f', name = "force", hasValue = false, description = "Force installation of wrapper even if files already exist") + boolean force; + + @Override + public Integer doCall() { + Path destPath = dest != null ? Paths.get(dest) : Paths.get("."); + if (!Files.isDirectory(destPath)) { + throw new ExitException(EXIT_INVALID_INPUT, "Destination folder does not exist"); } - if (!jar.toString().endsWith(".jar") && !jar.toString().endsWith(exeName)) { - throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't find JBang install location via " + jar); + if ((checkScripts(destPath) || checkJar(destPath.resolve(DIR_NAME))) && !force) { + Util.warnMsg("Wrapper already exists. Use --force to install anyway"); + return EXIT_OK; } - Path parent = jar.getParent(); - if (checkScripts(parent) && checkJar(parent)) { - copyScripts(parent, dest); - copyJar(parent, dest); - } else if (parent.getFileName().toString().equals(DIR_NAME) - && checkScripts(parent.getParent()) - && checkJar(parent)) { - copyScripts(parent.getParent(), dest); - copyJar(parent, dest); - } else { - throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't find JBang wrapper files"); + try { + Path jar = Util.getJarLocation(); + String exeName = "jbang.bin"; + if (Util.isWindows()) { + exeName = "jbang.bin.exe"; + } + if (!jar.toString().endsWith(".jar") && !jar.toString().endsWith(exeName)) { + throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't find JBang install location via " + jar); + } + Path parent = jar.getParent(); + if (checkScripts(parent) && checkJar(parent)) { + copyScripts(parent, destPath); + copyJar(parent, destPath); + } else if (parent.getFileName().toString().equals(DIR_NAME) + && checkScripts(parent.getParent()) + && checkJar(parent)) { + copyScripts(parent.getParent(), destPath); + copyJar(parent, destPath); + } else { + throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't find JBang wrapper files"); + } + return EXIT_OK; + } catch (IOException e) { + throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't copy JBang wrapper scripts", e); } - return EXIT_OK; - } catch (IOException e) { - throw new ExitException(EXIT_GENERIC_ERROR, "Couldn't copy JBang wrapper scripts", e); } - } - private boolean checkScripts(Path dir) { - return SCRIPT_NAMES.stream().map(dir::resolve).allMatch(Files::isRegularFile); - } + private boolean checkScripts(Path dir) { + return SCRIPT_NAMES.stream().map(dir::resolve).allMatch(Files::isRegularFile); + } - private boolean checkJar(Path dir) { - return Files.isRegularFile(dir.resolve(JAR_NAME)); - } + private boolean checkJar(Path dir) { + return Files.isRegularFile(dir.resolve(JAR_NAME)); + } - private void copyScripts(Path dir, Path dest) throws IOException { - for (String nm : SCRIPT_NAMES) { - Files.copy(dir.resolve(nm), dest.resolve(nm), COPY_ATTRIBUTES, REPLACE_EXISTING); + private void copyScripts(Path dir, Path dest) throws IOException { + for (String nm : SCRIPT_NAMES) { + Files.copy(dir.resolve(nm), dest.resolve(nm), COPY_ATTRIBUTES, REPLACE_EXISTING); + } } - } - private void copyJar(Path dir, Path dest) throws IOException { - Path jbdir = dest.resolve(DIR_NAME); - jbdir.toFile().mkdirs(); - Files.copy(dir.resolve(JAR_NAME), jbdir.resolve(JAR_NAME), COPY_ATTRIBUTES, REPLACE_EXISTING); + private void copyJar(Path dir, Path dest) throws IOException { + Path jbdir = dest.resolve(DIR_NAME); + jbdir.toFile().mkdirs(); + Files.copy(dir.resolve(JAR_NAME), jbdir.resolve(JAR_NAME), COPY_ATTRIBUTES, REPLACE_EXISTING); + } } } diff --git a/src/main/java/dev/jbang/util/ConsoleOutput.java b/src/main/java/dev/jbang/util/ConsoleOutput.java index 659afba12..e3c66eecd 100644 --- a/src/main/java/dev/jbang/util/ConsoleOutput.java +++ b/src/main/java/dev/jbang/util/ConsoleOutput.java @@ -1,25 +1,44 @@ package dev.jbang.util; -import picocli.CommandLine; - public class ConsoleOutput { + + private static final boolean ANSI_ENABLED = isAnsiEnabled(); + + private static boolean isAnsiEnabled() { + String term = System.getenv("TERM"); + if (System.console() == null) { + return false; + } + if ("dumb".equals(term)) { + return false; + } + return true; + } + + private static String ansi(String code, String text) { + if (ANSI_ENABLED) { + return "\033[" + code + "m" + text + "\033[0m"; + } + return text; + } + public static String yellow(String text) { - return CommandLine.Help.Ansi.AUTO.new Text("@|fg(yellow) " + text + "|@").toString(); + return ansi("33", text); } public static String cyan(String text) { - return CommandLine.Help.Ansi.AUTO.new Text("@|fg(cyan) " + text + "|@").toString(); + return ansi("36", text); } public static String magenta(String text) { - return CommandLine.Help.Ansi.AUTO.new Text("@|fg(magenta) " + text + "|@").toString(); + return ansi("35", text); } public static String faint(String text) { - return CommandLine.Help.Ansi.AUTO.new Text("@|faint " + text + "|@").toString(); + return ansi("2", text); } public static String bold(String text) { - return CommandLine.Help.Ansi.AUTO.new Text("@|bold " + text + "|@").toString(); + return ansi("1", text); } } diff --git a/src/native-image/config/reachability-metadata.json b/src/native-image/config/reachability-metadata.json index 5ad100f92..fff122a2c 100644 --- a/src/native-image/config/reachability-metadata.json +++ b/src/native-image/config/reachability-metadata.json @@ -2,1740 +2,687 @@ "reflection": [ { "type": "java.util.HashMap" }, { "type": "java.util.Map" }, - { "type": "java.util.ArrayList" }, - { "type": "java.lang.Cloneable" }, - { "type": "java.io.Serializable" }, - { - "type": "ch.qos.logback.classic.Logger" - }, - { - "type": "com.google.gson.internal.LinkedTreeMap" - }, { - "type": "dev.jbang.spi.IntegrationInput", - "allPublicFields" : true, - "allDeclaredConstructors" : true - }, - { - "type": "dev.jbang.spi.IntegrationResult", - "allPublicFields" : true, - "allDeclaredConstructors" : true, - "unsafeAllocated" : true - }, - { - "type": "dev.jbang.catalog.Alias", - "fields": [ - { - "name": "arguments" - }, - { - "name": "cds" - }, - { - "name": "classpaths" - }, - { - "name": "compileOptions" - }, - { - "name": "debug" - }, - { - "name": "dependencies" - }, - { - "name": "description" - }, - { - "name": "docs" - }, - { - "name": "enableAssertions" - }, - { - "name": "enablePreview" - }, - { - "name": "enableSystemAssertions" - }, - { - "name": "forceType" - }, - { - "name": "integrations" - }, - { - "name": "interactive" - }, - { - "name": "javaAgents" - }, - { - "name": "javaVersion" - }, - { - "name": "jfr" - }, - { - "name": "mainClass" - }, - { - "name": "manifestOptions" - }, - { - "name": "moduleName" - }, - { - "name": "nativeImage" - }, - { - "name": "nativeOptions" - }, - { - "name": "properties" - }, - { - "name": "repositories" - }, - { - "name": "resources" - }, - { - "name": "runtimeOptions" - }, - { - "name": "scriptRef" - }, - { - "name": "sources" - } - ], + "type": "java.util.ArrayList", "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.catalog.Alias$JavaAgent", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true, - "fields": [ - { - "name": "agentRef" - }, - { - "name": "options" - } - ] - }, - { - "type": "dev.jbang.catalog.Catalog", - "allPublicFields" : true, - "unsafeAllocated": true, - "fields": [ - { - "name": "aliases" - }, - { - "name": "baseRef" - }, - { - "name": "catalogs" - }, - { - "name": "description" - }, - { - "name": "templates" - } + { "name": "", "parameterTypes": [] } ] }, + { "type": "java.lang.Cloneable" }, + { "type": "java.io.Serializable" }, + { "type": "java.lang.Object" }, { - "type": "dev.jbang.catalog.Catalog$SkipEmptyListSerializer", + "type": "java.lang.Class", "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "isRecord", "parameterTypes": [] } ] }, + { "type": "java.lang.reflect.RecordComponent" }, { - "type": "dev.jbang.catalog.Catalog$SkipEmptyMapSerializer", + "type": "java.lang.Boolean", + "jniAccessible": true, "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.catalog.CatalogItem" - }, - { - "type": "dev.jbang.catalog.CatalogRef", - "unsafeAllocated": true, - "fields": [ - { - "name": "catalogRef" - }, - { - "name": "description" - }, - { - "name": "importItems" - } + { "name": "getBoolean", "parameterTypes": ["java.lang.String"] } ] }, + { "type": "java.sql.Date" }, + { "type": "java.security.AlgorithmParametersSpi" }, + { "type": "java.security.KeyStoreSpi" }, + { "type": "java.security.interfaces.ECPrivateKey" }, + { "type": "java.security.interfaces.ECPublicKey" }, + { "type": "java.security.interfaces.RSAPrivateKey" }, + { "type": "java.security.interfaces.RSAPublicKey" }, { - "type": "dev.jbang.catalog.Template", - "unsafeAllocated": true, - "fields": [ - { - "name": "description" - }, - { - "name": "fileRefs" - }, - { - "name": "properties" - } - ] - }, - { - "type": "dev.jbang.catalog.TemplateProperty", - "unsafeAllocated": true - }, - { - "type": "dev.jbang.cli.Alias", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.AIOptions" - }, - { - "type": "dev.jbang.cli.Deps" - }, - { - "type": "dev.jbang.cli.DepsAdd" - }, - { - "type": "dev.jbang.cli.DepsSearch" - }, - { - "type": "dev.jbang.search.MvnSearchResult", - "allDeclaredConstructors" : true, - "allPublicConstructors" : true, - "allPublicFields" : true + "type": "ch.qos.logback.classic.Logger" }, { - "type": "dev.jbang.search.MsrHeader", - "allDeclaredConstructors" : true, - "allPublicConstructors" : true, - "allPublicFields" : true + "type": "com.google.gson.internal.LinkedTreeMap" }, { - "type": "dev.jbang.search.MsrResponse", - "allDeclaredConstructors" : true, - "allPublicConstructors" : true, - "allPublicFields" : true + "type": "com.sun.crypto.provider.AESCipher$General", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.ai.AIProvider", - "allDeclaredConstructors" : true, - "allPublicConstructors" : true, - "allPublicFields" : true + "type": "com.sun.crypto.provider.ARCFOURCipher", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.search.MsrDoc", - "allDeclaredConstructors" : true, - "allPublicConstructors" : true, - "allPublicFields" : true + "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.AliasAdd", - "fields": [ - { - "name": "buildMixin" - }, - { - "name": "dependencyInfoMixin" - }, - { - "name": "description" - }, - { - "name": "docs" - }, - { - "name": "enablePreviewRequested" - }, - { - "name": "force" - }, - { - "name": "name" - }, - { - "name": "nativeMixin" - }, - { - "name": "runMixin" - }, - { - "name": "scriptMixin" - }, - { - "name": "userParams" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.DESCipher", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.AliasList", - "fields": [ - { - "name": "catalogName" - }, - { - "name": "formatMixin" - }, - { - "name": "showOrigin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.DESedeCipher", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.AliasRemove", - "fields": [ - { - "name": "name" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.DHParameters", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.App", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.AppInstall", - "fields": [ - { - "name": "force" - }, - { - "name": "name" - }, - { - "name": "noBuild" - }, - { - "name": "runMixin" - }, - { - "name": "userParams" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA256", + "methods": [ { "name": "", "parameterTypes": ["javax.crypto.KDFParameters"] } ] }, { - "type": "dev.jbang.cli.AppList", - "fields": [ - { - "name": "formatMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384", + "methods": [ { "name": "", "parameterTypes": ["javax.crypto.KDFParameters"] } ] }, { - "type": "dev.jbang.cli.AppSetup", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.AppUninstall", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BaseAliasCommand", - "fields": [ - { - "name": "catalogFile" - }, - { - "name": "global" - } - ] + "type": "com.sun.crypto.provider.TlsKeyMaterialGenerator", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BaseBuildCommand", - "fields": [ - { - "name": "buildDir" - }, - { - "name": "buildMixin" - }, - { - "name": "dependencyInfoMixin" - }, - { - "name": "enablePreviewRequested" - }, - { - "name": "nativeMixin" - }, - { - "name": "scriptMixin" - } - ] + "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BaseCatalogCommand", - "fields": [ - { - "name": "catalogFile" - }, - { - "name": "global" - } - ] + "type": "com.sun.crypto.provider.TlsPrfGenerator$V12", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BaseCommand", - "fields": [ - { - "name": "helpMixin" - }, - { - "name": "spec" - } - ] + "type": "dev.jbang.spi.IntegrationInput", + "allPublicFields": true, + "allDeclaredConstructors": true }, { - "type": "dev.jbang.cli.BaseConfigCommand", - "fields": [ - { - "name": "configFile" - }, - { - "name": "global" - } - ] + "type": "dev.jbang.spi.IntegrationResult", + "allPublicFields": true, + "allDeclaredConstructors": true, + "unsafeAllocated": true }, { - "type": "dev.jbang.cli.BaseExportCommand", + "type": "dev.jbang.catalog.Alias", "fields": [ - { - "name": "exportMixin" - } + { "name": "arguments" }, + { "name": "cds" }, + { "name": "classpaths" }, + { "name": "compileOptions" }, + { "name": "debug" }, + { "name": "dependencies" }, + { "name": "description" }, + { "name": "docs" }, + { "name": "enableAssertions" }, + { "name": "enablePreview" }, + { "name": "enableSystemAssertions" }, + { "name": "forceType" }, + { "name": "integrations" }, + { "name": "interactive" }, + { "name": "javaAgents" }, + { "name": "javaVersion" }, + { "name": "jfr" }, + { "name": "mainClass" }, + { "name": "manifestOptions" }, + { "name": "moduleName" }, + { "name": "nativeImage" }, + { "name": "nativeOptions" }, + { "name": "properties" }, + { "name": "repositories" }, + { "name": "resources" }, + { "name": "runtimeOptions" }, + { "name": "scriptRef" }, + { "name": "sources" } + ], + "methods": [ + { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BaseExportProject" + "type": "dev.jbang.catalog.Alias$JavaAgent", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true }, { - "type": "dev.jbang.cli.BaseInfoCommand", + "type": "dev.jbang.catalog.Catalog", + "allPublicFields": true, + "unsafeAllocated": true, "fields": [ - { - "name": "buildDir" - }, - { - "name": "dependencyInfoMixin" - }, - { - "name": "module" - }, - { - "name": "scriptMixin" - } + { "name": "aliases" }, + { "name": "baseRef" }, + { "name": "catalogs" }, + { "name": "description" }, + { "name": "templates" } ] }, { - "type": "dev.jbang.cli.BaseTemplateCommand", - "fields": [ - { - "name": "catalogFile" - }, - { - "name": "global" - } - ] + "type": "dev.jbang.catalog.Catalog$SkipEmptyListSerializer", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Build", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.catalog.Catalog$SkipEmptyMapSerializer", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.BuildMixin", - "fields": [ - { - "name": "compileOptions" - }, - { - "name": "integrations" - }, - { - "name": "jdkProvidersMixin" - }, - { - "name": "main" - }, - { - "name": "manifestOptions" - }, - { - "name": "module" - }, - { - "name": "spec" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setJavaVersion", - "parameterTypes": [ - "java.lang.String" - ] - } - ] + "type": "dev.jbang.catalog.CatalogItem" }, { - "type": "dev.jbang.cli.Cache", + "type": "dev.jbang.catalog.CatalogRef", + "unsafeAllocated": true, "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "clear", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "java.lang.Boolean", - "boolean" - ] - } + { "name": "catalogRef" }, + { "name": "description" }, + { "name": "importItems" } ] }, { - "type": "dev.jbang.cli.Catalog", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.CatalogAdd", + "type": "dev.jbang.catalog.Template", + "unsafeAllocated": true, "fields": [ - { - "name": "description" - }, - { - "name": "force" - }, - { - "name": "importItems" - }, - { - "name": "name" - }, - { - "name": "urlOrFile" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "description" }, + { "name": "fileRefs" }, + { "name": "properties" } ] }, { - "type": "dev.jbang.cli.CatalogList", - "fields": [ - { - "name": "formatMixin" - }, - { - "name": "name" - }, - { - "name": "showOrigin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.catalog.TemplateProperty", + "unsafeAllocated": true }, { - "type": "dev.jbang.cli.CatalogRemove", + "type": "dev.jbang.cli.BaseCommand", "fields": [ - { - "name": "name" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.CatalogUpdate", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ClassPath", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.CommaSeparatedConverter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, + { "name": "fresh" }, + { "name": "offline" }, + { "name": "preview" }, + { "name": "printExceptions" }, + { "name": "quiet" }, + { "name": "verbose" } + ] + }, + { "type": "dev.jbang.cli.BaseBuildCommand" }, + { "type": "dev.jbang.cli.Alias" }, + { "type": "dev.jbang.cli.Alias$AliasAdd" }, + { "type": "dev.jbang.cli.Alias$AliasList" }, + { "type": "dev.jbang.cli.Alias$AliasRemove" }, + { "type": "dev.jbang.cli.App" }, + { "type": "dev.jbang.cli.App$AppInstall" }, + { "type": "dev.jbang.cli.App$AppList" }, + { "type": "dev.jbang.cli.App$AppSetup" }, + { "type": "dev.jbang.cli.App$AppUninstall" }, + { "type": "dev.jbang.cli.Build" }, + { "type": "dev.jbang.cli.Cache" }, + { "type": "dev.jbang.cli.Cache$CacheClear" }, + { "type": "dev.jbang.cli.Catalog" }, + { "type": "dev.jbang.cli.Catalog$BaseCatalogCommand" }, + { "type": "dev.jbang.cli.Catalog$CatalogAdd" }, + { "type": "dev.jbang.cli.Catalog$CatalogList" }, + { "type": "dev.jbang.cli.Catalog$CatalogRemove" }, + { "type": "dev.jbang.cli.Catalog$CatalogUpdate" }, { "type": "dev.jbang.cli.Completion", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.Config", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ConfigGet", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ConfigList", - "fields": [ - { - "name": "formatMixin" - }, - { - "name": "showAvailable" - }, - { - "name": "showOrigin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ConfigSet", - "fields": [ - { - "name": "key" - }, - { - "name": "value" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ConfigUnset", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.DependencyInfoMixin", - "fields": [ - { - "name": "classpaths" - }, - { - "name": "dependencies" - }, - { - "name": "properties" - }, - { - "name": "repositories" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.Docs", - "fields": [ - { - "name": "open" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, + "fields": [ { "name": "shell" } ] + }, + { "type": "dev.jbang.cli.Config" }, + { "type": "dev.jbang.cli.Config$BaseConfigCommand" }, + { "type": "dev.jbang.cli.Config$ConfigGet" }, + { "type": "dev.jbang.cli.Config$ConfigList" }, + { "type": "dev.jbang.cli.Config$ConfigSet" }, + { "type": "dev.jbang.cli.Config$ConfigUnset" }, + { "type": "dev.jbang.cli.Deps" }, + { "type": "dev.jbang.cli.Deps$DepsAdd" }, + { "type": "dev.jbang.cli.Deps$DepsSearch" }, + { "type": "dev.jbang.cli.Edit" }, + { "type": "dev.jbang.cli.Export" }, + { "type": "dev.jbang.cli.Export$BaseExportCommand" }, + { "type": "dev.jbang.cli.Export$BaseExportProject" }, + { "type": "dev.jbang.cli.Export$ExportFatjar" }, + { "type": "dev.jbang.cli.Export$ExportGradleProject" }, + { "type": "dev.jbang.cli.Export$ExportJlink" }, + { "type": "dev.jbang.cli.Export$ExportLocal" }, + { "type": "dev.jbang.cli.Export$ExportMavenProject" }, + { "type": "dev.jbang.cli.Export$ExportMavenPublish" }, + { "type": "dev.jbang.cli.Export$ExportNative" }, + { "type": "dev.jbang.cli.Export$ExportPortable" }, + { "type": "dev.jbang.cli.Info" }, + { "type": "dev.jbang.cli.Info$BaseInfoCommand" }, + { + "type": "dev.jbang.cli.Info$BaseInfoCommand$ProjectFile", + "fields": [ + { "name": "backingResource" }, + { "name": "error" }, + { "name": "originalResource" }, + { "name": "target" } + ] + }, + { "type": "dev.jbang.cli.Info$BaseInfoCommand$Repo" }, + { + "type": "dev.jbang.cli.Info$BaseInfoCommand$ScriptInfo", + "fields": [ + { "name": "applicationJar" }, + { "name": "applicationJsa" }, + { "name": "availableJdkPath" }, + { "name": "backingResource" }, + { "name": "compileOptions" }, + { "name": "dependencies" }, + { "name": "description" }, + { "name": "docs" }, + { "name": "files" }, + { "name": "gav" }, + { "name": "javaVersion" }, + { "name": "mainClass" }, + { "name": "module" }, + { "name": "nativeImage" }, + { "name": "originalResource" }, + { "name": "repositories" }, + { "name": "requestedJavaVersion" }, + { "name": "resolvedDependencies" }, + { "name": "runtimeOptions" }, + { "name": "sources" } + ] + }, + { "type": "dev.jbang.cli.Info$ClassPath" }, + { "type": "dev.jbang.cli.Info$Docs" }, + { "type": "dev.jbang.cli.Info$Jar" }, + { "type": "dev.jbang.cli.Info$Tools" }, + { "type": "dev.jbang.cli.Init" }, + { "type": "dev.jbang.cli.JBang" }, + { "type": "dev.jbang.cli.Jdk" }, + { "type": "dev.jbang.cli.Jdk$JdkDefault" }, + { "type": "dev.jbang.cli.Jdk$JdkExec" }, + { "type": "dev.jbang.cli.Jdk$JdkHome" }, + { "type": "dev.jbang.cli.Jdk$JdkInstall" }, + { "type": "dev.jbang.cli.Jdk$JdkJavaEnv" }, + { "type": "dev.jbang.cli.Jdk$JdkList" }, + { "type": "dev.jbang.cli.Jdk$JdkUninstall" }, + { "type": "dev.jbang.cli.Run" }, + { "type": "dev.jbang.cli.Template" }, + { "type": "dev.jbang.cli.Template$BaseTemplateCommand" }, + { "type": "dev.jbang.cli.Template$TemplateAdd" }, + { "type": "dev.jbang.cli.Template$TemplateList" }, + { "type": "dev.jbang.cli.Template$TemplateRemove" }, + { "type": "dev.jbang.cli.Trust" }, + { "type": "dev.jbang.cli.Trust$TrustAdd" }, + { "type": "dev.jbang.cli.Trust$TrustList" }, + { "type": "dev.jbang.cli.Trust$TrustRemove" }, + { "type": "dev.jbang.cli.Version" }, + { "type": "dev.jbang.cli.Wrapper" }, + { "type": "dev.jbang.cli.Wrapper$WrapperInstall" }, + { "type": "dev.jbang.cli.Alias_AeshMetadata" }, + { "type": "dev.jbang.cli.Alias_AliasAdd_AeshMetadata" }, + { "type": "dev.jbang.cli.Alias_AliasList_AeshMetadata" }, + { "type": "dev.jbang.cli.Alias_AliasRemove_AeshMetadata" }, + { "type": "dev.jbang.cli.App_AeshMetadata" }, + { "type": "dev.jbang.cli.App_AppInstall_AeshMetadata" }, + { "type": "dev.jbang.cli.App_AppList_AeshMetadata" }, + { "type": "dev.jbang.cli.App_AppSetup_AeshMetadata" }, + { "type": "dev.jbang.cli.App_AppUninstall_AeshMetadata" }, + { "type": "dev.jbang.cli.Build_AeshMetadata" }, + { "type": "dev.jbang.cli.Cache_AeshMetadata" }, + { "type": "dev.jbang.cli.Cache_CacheClear_AeshMetadata" }, + { "type": "dev.jbang.cli.Catalog_AeshMetadata" }, + { "type": "dev.jbang.cli.Catalog_CatalogAdd_AeshMetadata" }, + { "type": "dev.jbang.cli.Catalog_CatalogList_AeshMetadata" }, + { "type": "dev.jbang.cli.Catalog_CatalogRemove_AeshMetadata" }, + { "type": "dev.jbang.cli.Catalog_CatalogUpdate_AeshMetadata" }, + { "type": "dev.jbang.cli.Completion_AeshMetadata" }, + { "type": "dev.jbang.cli.Config_AeshMetadata" }, + { "type": "dev.jbang.cli.Config_ConfigGet_AeshMetadata" }, + { "type": "dev.jbang.cli.Config_ConfigList_AeshMetadata" }, + { "type": "dev.jbang.cli.Config_ConfigSet_AeshMetadata" }, + { "type": "dev.jbang.cli.Config_ConfigUnset_AeshMetadata" }, + { "type": "dev.jbang.cli.Deps_AeshMetadata" }, + { "type": "dev.jbang.cli.Deps_DepsAdd_AeshMetadata" }, + { "type": "dev.jbang.cli.Deps_DepsSearch_AeshMetadata" }, + { "type": "dev.jbang.cli.Edit_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportFatjar_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportGradleProject_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportJlink_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportLocal_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportMavenProject_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportMavenPublish_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportNative_AeshMetadata" }, + { "type": "dev.jbang.cli.Export_ExportPortable_AeshMetadata" }, + { "type": "dev.jbang.cli.Info_AeshMetadata" }, + { "type": "dev.jbang.cli.Info_ClassPath_AeshMetadata" }, + { "type": "dev.jbang.cli.Info_Docs_AeshMetadata" }, + { "type": "dev.jbang.cli.Info_Jar_AeshMetadata" }, + { "type": "dev.jbang.cli.Info_Tools_AeshMetadata" }, + { "type": "dev.jbang.cli.Init_AeshMetadata" }, + { "type": "dev.jbang.cli.JBang_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkDefault_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkExec_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkHome_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkInstall_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkJavaEnv_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkList_AeshMetadata" }, + { "type": "dev.jbang.cli.Jdk_JdkUninstall_AeshMetadata" }, + { "type": "dev.jbang.cli.Run_AeshMetadata" }, + { "type": "dev.jbang.cli.Template_AeshMetadata" }, + { "type": "dev.jbang.cli.Template_TemplateAdd_AeshMetadata" }, + { "type": "dev.jbang.cli.Template_TemplateList_AeshMetadata" }, + { "type": "dev.jbang.cli.Template_TemplateRemove_AeshMetadata" }, + { "type": "dev.jbang.cli.Trust_AeshMetadata" }, + { "type": "dev.jbang.cli.Trust_TrustAdd_AeshMetadata" }, + { "type": "dev.jbang.cli.Trust_TrustList_AeshMetadata" }, + { "type": "dev.jbang.cli.Trust_TrustRemove_AeshMetadata" }, + { "type": "dev.jbang.cli.Version_AeshMetadata" }, + { "type": "dev.jbang.cli.Wrapper_AeshMetadata" }, + { "type": "dev.jbang.cli.Wrapper_WrapperInstall_AeshMetadata" }, { - "type": "dev.jbang.cli.Edit", - "fields": [ - { - "name": "additionalFiles" - }, - { - "name": "dependencyInfoMixin" - }, - { - "name": "editor" - }, - { - "name": "live" - }, - { - "name": "noOpen" - }, - { - "name": "sandbox" - }, - { - "name": "scriptMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.Export", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ExportFatjar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ExportGradleProject", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.cli.ExportJlink", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.ai.AIProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allPublicFields": true }, { - "type": "dev.jbang.cli.ExportLocal", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.search.MvnSearchResult", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allPublicFields": true }, { - "type": "dev.jbang.cli.ExportMavenProject", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.search.MsrHeader", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allPublicFields": true }, { - "type": "dev.jbang.cli.ExportMavenPublish", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.search.MsrResponse", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allPublicFields": true }, { - "type": "dev.jbang.cli.ExportMixin", - "fields": [ - { - "name": "buildMixin" - }, - { - "name": "dependencyInfoMixin" - }, - { - "name": "force" - }, - { - "name": "nativeImage" - }, - { - "name": "outputFile" - }, - { - "name": "scriptMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.search.MsrDoc", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allPublicFields": true }, { - "type": "dev.jbang.cli.ExportNative", + "type": "dev.jbang.dependencies.ArtifactInfo", "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "getCoordinate", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.ExportPortable", + "type": "dev.jbang.dependencies.MavenCoordinate", "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "getArtifactId", "parameterTypes": [] }, + { "name": "getGroupId", "parameterTypes": [] }, + { "name": "getVersion", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.FormatMixin", - "fields": [ - { - "name": "format" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "dev.jbang.util.JBangFormatter", + "allPublicFields": true, + "allPublicConstructors": true }, { - "type": "dev.jbang.cli.HelpMixin", + "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResult", "fields": [ - { - "name": "helpRequested" - } + { "name": "java_version" }, + { "name": "javafx_bundled" }, + { "name": "links" }, + { "name": "major_version" }, + { "name": "package_type" }, + { "name": "release_status" } ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Info", + "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResultLinks", "fields": [ - { - "name": "helpMixin" - } + { "name": "pkg_download_redirect" } ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Init", + "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$VersionsResponse", "fields": [ - { - "name": "buildMixin" - }, - { - "name": "dependencies" - }, - { - "name": "edit" - }, - { - "name": "force" - }, - { - "name": "initTemplate" - }, - { - "name": "params" - }, - { - "name": "properties" - }, - { - "name": "scriptOrFile" - } + { "name": "result" } ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "methods": [ { "name": "", "parameterTypes": [] } ] }, + { "type": "dev.jbang.devkitman.jdkproviders.CurrentJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.DefaultJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.JBangJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.JavaHomeJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.LinkedJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.LinuxJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.MiseJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.MultiHomeJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.PathJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.ScoopJdkProvider$Discovery" }, + { "type": "dev.jbang.devkitman.jdkproviders.SdkmanJdkProvider$Discovery" }, + { "type": "eu.maveniverse.maven.mima.runtime.standalonestatic.StandaloneStaticRuntime" }, { - "type": "dev.jbang.cli.JBang", - "fields": [ - { - "name": "offlineFreshExclusive" - }, - { - "name": "verboseQuietExclusive" - }, - { - "name": "versionRequested" - } - ] + "type": "org.aesh.command.impl.parser.AeshOptionParser", + "methods": [ { "name": "", "parameterTypes": [] } ] }, + { "type": "org.apache.log4j.LogManager" }, + { "type": "org.apache.logging.log4j.Logger" }, + { "type": "org.jboss.logmanager.LogManager" }, { - "type": "dev.jbang.cli.JBang$OfflineFreshExclusive", + "type": "org.apache.maven.model.Build", "methods": [ - { - "name": "setFresh", - "parameterTypes": [ - "boolean" - ] - } + { "name": "getOutputDirectory", "parameterTypes": [] }, + { "name": "getSourceDirectory", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.JBang$VerboseQuietExclusive", + "type": "org.apache.maven.model.BuildBase", "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setVerbose", - "parameterTypes": [ - "boolean" - ] - } + { "name": "getDirectory", "parameterTypes": [] }, + { "name": "getFinalName", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Jar", + "type": "org.apache.maven.model.Model", "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "getArtifactId", "parameterTypes": [] }, + { "name": "getBuild", "parameterTypes": [] }, + { "name": "getGroupId", "parameterTypes": [] }, + { "name": "getPackaging", "parameterTypes": [] }, + { "name": "getVersion", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Jdk", - "fields": [ - { - "name": "helpMixin" - }, - { - "name": "jdkProvidersMixin" - }, - { - "name": "spec" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "org.jline.terminal.impl.exec.ExecTerminalProvider", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.JdkProvidersMixin", - "fields": [ - { - "name": "jdkProviders" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "org.jline.terminal.impl.jansi.JansiTerminalProvider", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.KeyValueConsumer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "org.jline.terminal.impl.jna.JnaTerminalProvider", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.NativeMixin", - "fields": [ - { - "name": "nativeImage" - }, - { - "name": "nativeOptions" - } - ], + "type": "sun.misc.Unsafe", + "fields": [ { "name": "theUnsafe" } ], "methods": [ - { - "name": "", - "parameterTypes": [] - } + { "name": "allocateInstance", "parameterTypes": ["java.lang.Class"] } ] }, { - "type": "dev.jbang.cli.Run", - "fields": [ - { - "name": "literalScript" - }, - { - "name": "runMixin" - }, - { - "name": "userParams" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Run$DebugFallbackConsumer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Run$KeyValueFallbackConsumer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.RunMixin", - "fields": [ - { - "name": "cds" - }, - { - "name": "debugString" - }, - { - "name": "enableAssertions" - }, - { - "name": "enableSystemAssertions" - }, - { - "name": "flightRecorderString" - }, - { - "name": "interactive" - }, - { - "name": "javaAgentSlots" - }, - { - "name": "javaRuntimeOptions" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.ScriptMixin", - "fields": [ - { - "name": "catalog" - }, - { - "name": "forceType" - }, - { - "name": "resources" - }, - { - "name": "scriptOrFile" - }, - { - "name": "sources" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setForcejsh", - "parameterTypes": [ - "boolean" - ] - } - ] + "type": "sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.StrictParameterPreprocessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.JavaKeyStore$JKS", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Template", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.MD5", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.TemplateAdd", - "fields": [ - { - "name": "description" - }, - { - "name": "fileRefs" - }, - { - "name": "force" - }, - { - "name": "name" - }, - { - "name": "properties" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.NativePRNG", + "methods": [ { "name": "", "parameterTypes": ["java.security.SecureRandomParameters"] } ] }, { - "type": "dev.jbang.cli.TemplateList", - "fields": [ - { - "name": "catalogName" - }, - { - "name": "formatMixin" - }, - { - "name": "showFiles" - }, - { - "name": "showOrigin" - }, - { - "name": "showProperties" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.SHA", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.TemplatePropertyConverter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.SHA2$SHA224", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.TemplateRemove", - "fields": [ - { - "name": "name" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.SHA2$SHA256", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Tools", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.SHA5$SHA384", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Trust", - "fields": [ - { - "name": "helpMixin" - }, - { - "name": "spec" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "add", - "parameterTypes": [ - "java.util.List" - ] - } - ] + "type": "sun.security.provider.SHA5$SHA512", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Version", - "fields": [ - { - "name": "checkForUpdate" - }, - { - "name": "update" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.X509Factory", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.VersionProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.provider.certpath.PKIXCertPathValidator", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.cli.Wrapper", - "fields": [ - { - "name": "helpMixin" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "install", - "parameterTypes": [ - "java.nio.file.Path", - "boolean" - ] - } - ] - }, - { - "type": "dev.jbang.dependencies.ArtifactInfo", - "methods": [ - { - "name": "getCoordinate", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.dependencies.MavenCoordinate", - "methods": [ - { - "name": "getArtifactId", - "parameterTypes": [] - }, - { - "name": "getGroupId", - "parameterTypes": [] - }, - { - "name": "getVersion", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.jbang.util.JBangFormatter", - "allPublicFields" : true, - "allPublicConstructors" : true - }, - { - "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResult", - "allPublicFields" : true, - "allPublicConstructors" : true - }, - { - "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResultLinks", - "allPublicFields" : true, - "allPublicConstructors" : true - }, - { - "type": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$VersionsResponse", - "allPublicFields" : true, - "allPublicConstructors" : true - }, - { - "type": "dev.jbang.devkitman.jdkproviders.CurrentJdkProvider$Discovery" - }, - { - "type": "dev.jbang.devkitman.jdkproviders.DefaultJdkProvider$Discovery" + "type": "sun.security.rsa.PSSParameters", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.JBangJdkProvider$Discovery" + "type": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.JavaHomeJdkProvider$Discovery" + "type": "sun.security.rsa.RSAPSSSignature", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.LinkedJdkProvider$Discovery" + "type": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.LinuxJdkProvider$Discovery" + "type": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.MiseJdkProvider$Discovery" + "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.MultiHomeJdkProvider$Discovery" + "type": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.PathJdkProvider$Discovery" + "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ { "name": "", "parameterTypes": [] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.ScoopJdkProvider$Discovery" + "type": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "dev.jbang.devkitman.jdkproviders.SdkmanJdkProvider$Discovery" + "type": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "env", - "fields": [ - { - "name": "pattern" - } - ] - }, - { - "type": "org.apache.maven.model.Build", - "methods": [ - { - "name": "getOutputDirectory", - "parameterTypes": [] - }, - { - "name": "getSourceDirectory", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.BasicConstraintsExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.apache.maven.model.BuildBase", - "methods": [ - { - "name": "getDirectory", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.apache.maven.model.Model", - "methods": [ - { - "name": "getArtifactId", - "parameterTypes": [] - }, - { - "name": "getBuild", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getGroupId", - "parameterTypes": [] - }, - { - "name": "getName", - "parameterTypes": [] - }, - { - "name": "getOrganization", - "parameterTypes": [] - }, - { - "name": "getPackaging", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - }, - { - "name": "getVersion", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.apache.maven.model.ModelBase", - "methods": [ - { - "name": "getReporting", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.apache.maven.model.Organization", - "methods": [ - { - "name": "getName", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.KeyUsageExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.apache.maven.model.Reporting", - "methods": [ - { - "name": "getOutputDirectory", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.jline.terminal.impl.exec.ExecTerminalProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.jline.terminal.impl.jansi.JansiTerminalProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, { - "type": "org.jline.terminal.impl.jna.JnaTerminalProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] + "type": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ { "name": "", "parameterTypes": ["java.lang.Boolean", "java.lang.Object"] } ] }, - { - "type": "picocli.CommandLine$AutoHelpMixin", - "fields": [ - { - "name": "helpRequested" - }, - { - "name": "versionRequested" - } - ] - } + { "type": "sun.text.resources.cldr.FormatData" }, + { "type": "sun.text.resources.cldr.FormatData_en" }, + { "type": "sun.text.resources.cldr.FormatData_en_US" }, + { "type": "sun.util.resources.cldr.CalendarData" }, + { "type": "sun.util.resources.cldr.TimeZoneNames" }, + { "type": "sun.util.resources.cldr.TimeZoneNames_en" }, + { "type": "sun.util.resources.cldr.TimeZoneNames_en_US" } ], "resources": [ + { "bundle": "consoleui_messages" }, + { "bundle": "sun.util.logging.resources.logging" }, + { "glob": "META-INF/services/**" }, + { "glob": "META-INF/jbang-integration.list" }, + { "glob": "META-INF/maven/org.apache.maven/maven-resolver-provider/pom.properties" }, + { "glob": "eu/maveniverse/maven/mima/runtime/shared/internal/version.properties" }, + { "glob": "jbang-catalog.json" }, + { "glob": "jbang.properties" }, + { "glob": "logging.properties" }, + { "glob": "mozilla/public-suffix-list.txt" }, + { "glob": "org/apache/maven/model/pom-4.0.0.xml" }, + { "glob": "org/slf4j/impl/StaticLoggerBinder.class" }, { - "bundle": "consoleui_messages" - }, - { - "glob": "META-INF/services/**" - }, - { - "glob": ".qute.classpath" - }, - { - "glob": ".qute.project" - }, - { - "glob": "META-INF/jbang-integration.list" - }, - { - "glob": "README.qute.md" - }, - { - "glob": "build.qute.gradle" - }, - { - "glob": "init-hello.java.qute" - }, - { - "glob": "jbang-catalog.json" - }, - { - "glob": "jbang.properties" - }, - { - "glob": "launch.qute.json" - }, - { - "glob": "locales/da-DK.yml" - }, - { - "glob": "locales/en.yml" - }, - { - "glob": "log4j.properties" - }, - { - "glob": "log4j.xml" - }, - { - "glob": "logging.properties" - }, - { - "glob": "main-port-4004.qute.launch" - }, - { - "glob": "main.qute.launch" - }, - { - "glob": "mozilla/public-suffix-list.txt" - }, - { - "glob": "org/apache/maven/model/pom-4.0.0.xml" - }, - { - "glob": "org/jline/utils/capabilities.txt" - }, - { - "glob": "org/jline/utils/dumb.caps" - }, - { - "glob": "org/slf4j/impl/StaticLoggerBinder.class" - }, - { - "glob": "pom.qute.xml" - }, - { - "glob": "resource.properties" - }, - { - "glob": "settings.qute.json" - }, - { - "glob": "standard.flf" + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" }, { - "glob": "trusted-sources.json.qute" + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/uprops.icu" }, { - "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" + "module": "java.base", + "glob": "sun/net/idn/uidna.spp" }, { "module": "java.logging", "glob": "sun/util/logging/resources/*.properties" } - - ] -} \ No newline at end of file +} diff --git a/src/native-image/config/reflect-config.json b/src/native-image/config/reflect-config.json deleted file mode 100644 index 70a196378..000000000 --- a/src/native-image/config/reflect-config.json +++ /dev/null @@ -1,93 +0,0 @@ -[ - { - "name": "dev.jbang.Main", - "methods": [ - {"name": "main", "parameterTypes": ["java.lang.String[]"]}, - {"name": "handleDefaultRun", "parameterTypes": ["picocli.CommandLine.Model.CommandSpec", "java.lang.String[]"]} - ] - }, - { - "name": "dev.jbang.cli.StrictParameterPreprocessor", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Catalog", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Alias", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Alias$JavaAgent", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Template", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.CatalogRef", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.CatalogItem", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Catalog$SkipEmptyMapSerializer", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "dev.jbang.catalog.Catalog$SkipEmptyListSerializer", - "allDeclaredConstructors": true, - "allDeclaredMethods": true, - "allDeclaredFields": true - }, - { - "name": "picocli.CommandLine", - "methods": [ - {"name": "execute", "parameterTypes": ["java.lang.String[]"]}, - {"name": "", "parameterTypes": ["java.lang.Object"]} - ] - }, - { - "name": "picocli.CommandLine$IFactory", - "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true - }, - { - "name": "picocli.CommandLine$Model$CommandSpec", - "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true - }, - { - "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$VersionsResponse", - "allDeclaredConstructors": true, - "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true - }, - { - "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResult", - "allDeclaredConstructors": true, - "queryAllDeclaredMethods": true, - "queryAllPublicMethods": true, - "allDeclaredFields": true - } -] diff --git a/src/test/java/dev/jbang/BaseTest.java b/src/test/java/dev/jbang/BaseTest.java index 5ad57e245..3ae69c48c 100644 --- a/src/test/java/dev/jbang/BaseTest.java +++ b/src/test/java/dev/jbang/BaseTest.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; -import java.util.function.Function; import java.util.logging.ConsoleHandler; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -46,8 +45,6 @@ import dev.jbang.dependencies.DependencyCache; import dev.jbang.util.Util; -import picocli.CommandLine; - public abstract class BaseTest { public Path jbangTempDir; public Path cwdDir; @@ -128,26 +125,17 @@ static void cleanupAfterAll() { Util.deletePath(jdksTempDir, true); } - protected CaptureResult checkedRun(Function commandRunner, String... args) - throws Exception { - CommandLine cli = JBang.getCommandLine(); - args = Main.handleDefaultRun(cli.getCommandSpec(), args); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs(args); - while (pr.subcommand() != null) { - pr = pr.subcommand(); - } - @SuppressWarnings("unchecked") - T usrobj = (T) pr.commandSpec().userObject(); - + protected CaptureResult checkedRun(String... args) throws Exception { return captureOutput(() -> { - if (commandRunner != null) { - return commandRunner.apply(usrobj); - } else if (usrobj instanceof BaseCommand) { - BaseCommand cmd = ((BaseCommand) usrobj); - cmd.realOut = System.out; // Reset the output stream to the original, just for testing + try { + BaseCommand cmd = JBang.parseCommand(args); + cmd.realOut = System.out; return cmd.doCall(); - } else { - throw new IllegalStateException("usrobj is of unsupported type"); + } catch (RuntimeException e) { + if (e.getCause() instanceof org.aesh.command.parser.CommandLineParserException) { + return JBang.execute(args); + } + throw e; } }); } diff --git a/src/test/java/dev/jbang/TestConfiguration.java b/src/test/java/dev/jbang/TestConfiguration.java index 75ae53b03..82e277c59 100644 --- a/src/test/java/dev/jbang/TestConfiguration.java +++ b/src/test/java/dev/jbang/TestConfiguration.java @@ -17,8 +17,6 @@ import dev.jbang.resources.ResourceRef; import dev.jbang.util.Util; -import picocli.CommandLine; - public class TestConfiguration extends BaseTest { static final String config = "foo = baz\n" + @@ -121,55 +119,48 @@ public void testInstanceOverride() throws IOException { @Test public void testCommandDefaults() { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("init", "dummy"); - Init init = (Init) pr.subcommand().commandSpec().userObject(); + Init init = JBang.parseCommand("init", "dummy"); assertThat(init.initTemplate, equalTo("bye")); } @Test public void testCommandEditOpenDefault() { environmentVariables.clear("JBANG_EDITOR"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "dummy"); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); - assertThat(edit.editor.get(), equalTo("someeditor.cfg")); + Edit edit = JBang.parseCommand("edit", "dummy"); + assertThat(edit.editor, equalTo("someeditor.cfg")); } @Test public void testCommandEditOpenDefaultEnv() { environmentVariables.set("JBANG_EDITOR", "someeditor.env"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "dummy"); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); - assertThat(edit.editor.get(), equalTo("someeditor.env")); + Edit edit = JBang.parseCommand("edit", "dummy"); + assertThat(edit.editor, equalTo("someeditor.env")); } @Test public void testCommandEditOpenFallback() { environmentVariables.clear("JBANG_EDITOR"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "--open", "dummy"); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); - assertThat(edit.editor.get(), equalTo("someeditor.cfg")); + Edit edit = JBang.parseCommand("edit", "--open", "dummy"); + assertThat(edit.editor, equalTo("someeditor.cfg")); } @Test public void testCommandEditOpenFallbackEnv() { environmentVariables.set("JBANG_EDITOR", "someeditor.env"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "--open", "dummy"); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); - assertThat(edit.editor.get(), equalTo("someeditor.env")); + Edit edit = JBang.parseCommand("edit", "--open", "dummy"); + assertThat(edit.editor, equalTo("someeditor.env")); } @Test public void testCommandFallbacks() { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "dummy"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "dummy"); assertThat(run.runMixin.debugString, is(nullValue())); assertThat(run.runMixin.flightRecorderString, emptyOrNullString()); } @Test public void testCommandFallbacks2() { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--jfr", "--debug", "dummy"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--jfr", "--debug", "dummy"); assertThat(run.runMixin.debugString, allOf(hasEntry("address", "4004"), is(aMapWithSize(1)))); assertThat(run.runMixin.flightRecorderString, equalTo("dummyjfr.cfg")); } diff --git a/src/test/java/dev/jbang/cli/TemplatePropertyConverterTest.java b/src/test/java/dev/jbang/cli/TemplatePropertyConverterTest.java index d3a5d02b8..0147cd6be 100644 --- a/src/test/java/dev/jbang/cli/TemplatePropertyConverterTest.java +++ b/src/test/java/dev/jbang/cli/TemplatePropertyConverterTest.java @@ -1,59 +1,67 @@ package dev.jbang.cli; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; -import java.util.stream.Stream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.Test; -class TemplatePropertyConverterTest { +import dev.jbang.catalog.TemplateProperty; - private final TemplatePropertyConverter underTest = new TemplatePropertyConverter(); +class TemplatePropertyConverterTest { - @ParameterizedTest - @MethodSource("propertyConverterTestCases") - void convertShouldReturnProperPropertyObjects(TemplatePropertyConverterTestCase testCase) throws Exception { - TemplateAdd.TemplatePropertyInput templatePropertyInput = underTest.convert(testCase.getInput()); - assertEquals(testCase.getExpected(), templatePropertyInput); + @Test + void testParseKeyOnly() { + Map result = Template.TemplateAdd.parseProperties( + Collections.singletonList("test-key")); + TemplateProperty prop = result.get("test-key"); + assertNull(prop.getDescription()); + assertNull(prop.getDefaultValue()); } - static Stream propertyConverterTestCases() { - return Stream.of( - Arguments.of(new TemplatePropertyConverterTestCase("test-key", - new TemplateAdd.TemplatePropertyInput("test-key", null, null))), - Arguments.of(new TemplatePropertyConverterTestCase("test-key::", - new TemplateAdd.TemplatePropertyInput("test-key", null, null))), - Arguments.of(new TemplatePropertyConverterTestCase("test-key:This is a description for the property", - new TemplateAdd.TemplatePropertyInput("test-key", "This is a description for the property", - null))), - Arguments.of(new TemplatePropertyConverterTestCase("test-key:This is a description for the property:", - new TemplateAdd.TemplatePropertyInput("test-key", "This is a description for the property", - null))), - Arguments.of( - new TemplatePropertyConverterTestCase("test-key:This is a description for the property:2.11", - new TemplateAdd.TemplatePropertyInput("test-key", - "This is a description for the property", "2.11"))), - Arguments.of(new TemplatePropertyConverterTestCase("test-key::2.11", - new TemplateAdd.TemplatePropertyInput("test-key", null, "2.11")))); + @Test + void testParseKeyWithDescription() { + Map result = Template.TemplateAdd.parseProperties( + Collections.singletonList("test-key=This is a description for the property")); + TemplateProperty prop = result.get("test-key"); + assertEquals("This is a description for the property", prop.getDescription()); + assertNull(prop.getDefaultValue()); } -} -class TemplatePropertyConverterTestCase { - private final String input; - private final TemplateAdd.TemplatePropertyInput expected; + @Test + void testParseKeyWithDescriptionAndDefault() { + Map result = Template.TemplateAdd.parseProperties( + Collections.singletonList("test-key=This is a description for the property::2.11")); + TemplateProperty prop = result.get("test-key"); + assertEquals("This is a description for the property", prop.getDescription()); + assertEquals("2.11", prop.getDefaultValue()); + } - public TemplatePropertyConverterTestCase(String input, TemplateAdd.TemplatePropertyInput expected) { - this.input = input; - this.expected = expected; + @Test + void testParseKeyWithDefaultOnly() { + Map result = Template.TemplateAdd.parseProperties( + Collections.singletonList("test-key=::2.11")); + TemplateProperty prop = result.get("test-key"); + assertNull(prop.getDescription()); + assertEquals("2.11", prop.getDefaultValue()); } - public String getInput() { - return input; + @Test + void testParseMultipleProperties() { + Map result = Template.TemplateAdd.parseProperties( + Arrays.asList("key1=desc1::val1", "key2=desc2")); + assertEquals("desc1", result.get("key1").getDescription()); + assertEquals("val1", result.get("key1").getDefaultValue()); + assertEquals("desc2", result.get("key2").getDescription()); + assertNull(result.get("key2").getDefaultValue()); } - public TemplateAdd.TemplatePropertyInput getExpected() { - return expected; + @Test + void testParseNullReturnsEmptyMap() { + Map result = Template.TemplateAdd.parseProperties(null); + assertEquals(0, result.size()); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/jbang/cli/TestAeshParsing.java b/src/test/java/dev/jbang/cli/TestAeshParsing.java new file mode 100644 index 000000000..02a566dd3 --- /dev/null +++ b/src/test/java/dev/jbang/cli/TestAeshParsing.java @@ -0,0 +1,222 @@ +package dev.jbang.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import dev.jbang.BaseTest; +import dev.jbang.Configuration; +import dev.jbang.util.Util; + +class TestAeshParsing extends BaseTest { + + // --- Mutual exclusion (exclusiveWith) --- + + @Test + void testVerboseAndQuietAreMutuallyExclusive() { + assertThrows(RuntimeException.class, + () -> JBang.parseCommand("run", "--verbose", "--quiet", "test.java")); + } + + @Test + void testOfflineAndFreshAreMutuallyExclusive() { + assertThrows(RuntimeException.class, + () -> JBang.parseCommand("run", "--offline", "--fresh", "test.java")); + } + + @Test + void testVerboseAloneWorks() { + JBang.parseCommand("run", "--verbose", "test.java"); + assertThat(Util.isVerbose(), is(true)); + assertThat(Util.isQuiet(), is(false)); + } + + @Test + void testQuietAloneWorks() { + JBang.parseCommand("run", "--quiet", "test.java"); + assertThat(Util.isQuiet(), is(true)); + assertThat(Util.isVerbose(), is(false)); + } + + @Test + void testOfflineAloneWorks() { + JBang.parseCommand("run", "--offline", "test.java"); + assertThat(Util.isOffline(), is(true)); + assertThat(Util.isFresh(), is(false)); + } + + @Test + void testFreshAloneWorks() { + JBang.parseCommand("run", "--fresh", "test.java"); + assertThat(Util.isFresh(), is(true)); + assertThat(Util.isOffline(), is(false)); + } + + // --- StrictOptionParser (optional value with = syntax) --- + + @Test + void testModuleWithEqualsValue() { + Run run = JBang.parseCommand("run", "--module=mymod", "test.java"); + assertThat(run.buildMixin.module, equalTo("mymod")); + } + + @Test + void testModuleWithEqualsEmpty() { + Run run = JBang.parseCommand("run", "--module=", "test.java"); + assertThat(run.buildMixin.module, equalTo("")); + } + + @Test + void testModuleNotSpecified() { + Run run = JBang.parseCommand("run", "test.java"); + assertThat(run.buildMixin.module, is(nullValue())); + } + + @Test + void testCodeWithEqualsValue() { + Run run = JBang.parseCommand("run", "-c=System.out.println(42)"); + assertThat(run.literalScript, equalTo("System.out.println(42)")); + } + + // --- Inherited options on subcommands --- + + @Test + void testVerboseInheritedOnBuild() { + JBang.parseCommand("build", "--verbose", "test.java"); + assertThat(Util.isVerbose(), is(true)); + } + + @Test + void testOfflineInheritedOnInit() { + JBang.parseCommand("init", "--offline", "test.java"); + assertThat(Util.isOffline(), is(true)); + } + + @Test + void testFreshInheritedOnEdit() { + JBang.parseCommand("edit", "--fresh", "test.java"); + assertThat(Util.isFresh(), is(true)); + } + + // --- Negatable options --- + + @Test + void testNegatableIntegrationsTrue() { + Run run = JBang.parseCommand("run", "--integrations", "test.java"); + assertThat(run.buildMixin.getIntegrations(), is(true)); + } + + @Test + void testNegatableIntegrationsFalse() { + Run run = JBang.parseCommand("run", "--no-integrations", "test.java"); + assertThat(run.buildMixin.getIntegrations(), is(false)); + } + + @Test + void testNegatableIntegrationsDefault() { + Run run = JBang.parseCommand("run", "test.java"); + assertThat(run.buildMixin.getIntegrations(), is(nullValue())); + } + + // --- @OptionGroup (map options like -D) --- + + @Test + void testPropertyOptionSingle() { + Run run = JBang.parseCommand("run", "-Dfoo=bar", "test.java"); + assertThat(run.dependencyInfoMixin.properties, hasEntry("foo", "bar")); + } + + @Test + void testPropertyOptionMultiple() { + Run run = JBang.parseCommand("run", "-Dfoo=bar", "-Dbaz=qux", "test.java"); + assertThat(run.dependencyInfoMixin.properties, hasEntry("foo", "bar")); + assertThat(run.dependencyInfoMixin.properties, hasEntry("baz", "qux")); + assertThat(run.dependencyInfoMixin.properties, is(aMapWithSize(2))); + } + + // --- @OptionList with valueSeparator --- + + @Test + void testDepsCommaSeparated() { + Run run = JBang.parseCommand("run", "--deps", "org.foo:bar:1.0,org.baz:qux:2.0", "test.java"); + assertThat(run.dependencyInfoMixin.dependencies, hasSize(2)); + assertThat(run.dependencyInfoMixin.dependencies, contains("org.foo:bar:1.0", "org.baz:qux:2.0")); + } + + @Test + void testDepsMultipleFlags() { + Run run = JBang.parseCommand("run", "--deps", "org.foo:bar:1.0", "--deps", "org.baz:qux:2.0", "test.java"); + assertThat(run.dependencyInfoMixin.dependencies, hasSize(2)); + assertThat(run.dependencyInfoMixin.dependencies, hasItems("org.foo:bar:1.0", "org.baz:qux:2.0")); + } + + // --- DefaultValueProvider with config --- + + static final String TEST_CONFIG = "init.template = mytemplate\n"; + + @Test + void testDefaultValueProviderAppliesConfigDefaults() throws IOException { + Files.write(jbangTempDir.resolve(Configuration.JBANG_CONFIG_PROPS), TEST_CONFIG.getBytes()); + Configuration.instance(null); + + Init init = JBang.parseCommand("init", "dummy"); + assertThat(init.initTemplate, equalTo("mytemplate")); + } + + @Test + void testDefaultValueProviderExplicitOverridesConfig() throws IOException { + Files.write(jbangTempDir.resolve(Configuration.JBANG_CONFIG_PROPS), TEST_CONFIG.getBytes()); + Configuration.instance(null); + + Init init = JBang.parseCommand("init", "--template", "other", "dummy"); + assertThat(init.initTemplate, equalTo("other")); + } + + @Test + void testDefaultValueProviderSkipsDebug() throws IOException { + String config = "run.debug = 5005\n"; + Files.write(jbangTempDir.resolve(Configuration.JBANG_CONFIG_PROPS), config.getBytes()); + Configuration.instance(null); + + Run run = JBang.parseCommand("run", "test.java"); + assertThat(run.runMixin.debugString, is(nullValue())); + } + + @Test + void testDefaultValueProviderSkipsJfr() throws IOException { + String config = "run.jfr = myrecording\n"; + Files.write(jbangTempDir.resolve(Configuration.JBANG_CONFIG_PROPS), config.getBytes()); + Configuration.instance(null); + + Run run = JBang.parseCommand("run", "test.java"); + assertThat(run.runMixin.flightRecorderString, emptyOrNullString()); + } + + // --- Hidden options not in help --- + + @Test + void testHiddenOptionNotInHelp() throws Exception { + CaptureResult result = captureOutput(() -> JBang.execute("run", "--help")); + String output = result.normalizedOut() + result.normalizedErr(); + assertThat(output, not(containsString("jdk-providers"))); + } + + // --- Help output contains expected options --- + + @Test + void testHelpShowsVisibleOptions() throws Exception { + CaptureResult result = captureOutput(() -> JBang.execute("run", "--help")); + String output = result.normalizedOut() + result.normalizedErr(); + assertThat(output, containsString("--verbose")); + assertThat(output, containsString("--quiet")); + assertThat(output, containsString("--offline")); + assertThat(output, containsString("--fresh")); + } +} diff --git a/src/test/java/dev/jbang/cli/TestAlias.java b/src/test/java/dev/jbang/cli/TestAlias.java index 15de03a1c..ec9fc97e8 100644 --- a/src/test/java/dev/jbang/cli/TestAlias.java +++ b/src/test/java/dev/jbang/cli/TestAlias.java @@ -22,8 +22,6 @@ import dev.jbang.catalog.Catalog; import dev.jbang.util.Util; -import picocli.CommandLine; - public class TestAlias extends BaseTest { static final String aliases = "{\n" + @@ -132,7 +130,7 @@ void testAddWithDefaultCatalogFile() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias alias = Alias.get("name"); assertThat(alias.scriptRef, is("test.java")); @@ -144,23 +142,22 @@ void testAddWithDefaultCatalogFile2() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("alias", "add", - "-f", cwd.toString(), - "--name=name", - "--description", "desc", - "--deps", "deps", - "--repos", "repos", - "--cp", "cps", - "--runtime-option", "jopts", - "-D", "prop=val", - "--main", "mainclass", - "--compile-option", "copts", - "--native", - "--native-option", "nopts", - "--java", "999", - testFile.toString(), - "aap", "noot", "mies"); + JBang.execute("alias", "add", + "-f", cwd.toString(), + "--name=name", + "--description", "desc", + "--deps", "deps", + "--repos", "repos", + "--cp", "cps", + "--runtime-option", "jopts", + "-Dprop=val", + "--main", "mainclass", + "--compile-option", "copts", + "--native", + "--native-option", "nopts", + "--java", "999", + testFile.toString(), + "aap", "noot", "mies"); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias alias = Alias.get("name"); @@ -194,22 +191,21 @@ void testAddWithDefaultCatalogFile3() throws IOException { Path catFile = Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON); Files.write(catFile, "".getBytes()); assertThat(Files.size(catFile), is(0L)); - JBang.getCommandLine() - .execute("alias", "add", - "--name=name", - "--description", "desc", - "--deps", "deps", - "--repos", "repos", - "--cp", "cps", - "--runtime-option", "jopts", - "-D", "prop=val", - "--main", "mainclass", - "--compile-option", "copts", - "--native-option", "nopts", - "--source-type", "java", - "--java", "999", - testFile.toString(), - "aap", "noot", "mies"); + JBang.execute("alias", "add", + "--name=name", + "--description", "desc", + "--deps", "deps", + "--repos", "repos", + "--cp", "cps", + "--runtime-option", "jopts", + "-Dprop=val", + "--main", "mainclass", + "--compile-option", "copts", + "--native-option", "nopts", + "--source-type", "java", + "--java", "999", + testFile.toString(), + "aap", "noot", "mies"); assertThat(Files.size(catFile), not(is(0L))); Alias alias = Alias.get("name"); assertThat(alias.scriptRef, is("test.java")); @@ -243,7 +239,7 @@ void testAddWithSubDir() throws IOException { Path testFile = sub.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias name = Alias.get("name"); assertThat(name.scriptRef, is(Paths.get("sub/test.java").toString())); @@ -256,7 +252,7 @@ void testNoEmptiesStored() throws IOException { Path testFile = sub.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); Path catalog = Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON); assertThat(Files.isRegularFile(catalog), is(true)); String catjson = Files.readString(catalog); @@ -272,7 +268,7 @@ void testAddWithDescriptionInScript() throws IOException { "//DESCRIPTION Description of the script inside the script") .getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias name = Alias.get("name"); assertThat(name.scriptRef, is("test.java")); @@ -287,9 +283,8 @@ void testAddWithDescriptionInArgs() throws IOException { "//DESCRIPTION Description of the script inside the script") .getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("alias", "add", "-f", cwd.toString(), "--name=name", - "--description", "Description of the script in arguments", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", + "--description", "Description of the script in arguments", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias name = Alias.get("name"); assertThat(name.scriptRef, is("test.java")); @@ -307,7 +302,7 @@ void testAddWithMultipleDescriptionTags() throws IOException { "//DESCRIPTION description third tag") .getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias name = Alias.get("name"); assertThat(name.scriptRef, is("test.java")); @@ -321,7 +316,7 @@ void testAddWithNoDescriptionTags() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, ("// Test file \n").getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias name = Alias.get("name"); assertThat(name.scriptRef, is("test.java")); @@ -337,7 +332,7 @@ void testAddWithHiddenJBangCatalog() throws IOException { Path hiddenJBangPath = Paths.get(cwd.toString(), Settings.JBANG_DOT_DIR); Files.createDirectory(hiddenJBangPath); Files.createFile(Paths.get(cwd.toString(), Settings.JBANG_DOT_DIR, Catalog.JBANG_CATALOG_JSON)); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); Catalog catalog = Catalog.get(hiddenJBangPath); Alias name = catalog.aliases.get("name"); @@ -349,7 +344,7 @@ void testAddPreservesExistingCatalog() throws IOException { Path cwd = Util.getCwd(); Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); - JBang.getCommandLine().execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); Alias one = Alias.get("one"); Alias name = Alias.get("name"); assertThat(one.scriptRef, is("http://dummy")); @@ -359,10 +354,8 @@ void testAddPreservesExistingCatalog() throws IOException { @Test void testAddWithRepos() throws IOException { String jar = "dummygroup:dummyart:0.1"; - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("alias", "add", "--name=aliaswithrepo", "--repos", - "https://dummyrepo", jar); - AliasAdd add = (AliasAdd) pr.subcommand().subcommand().commandSpec().userObject(); + dev.jbang.cli.Alias.AliasAdd add = JBang.parseCommand("alias", "add", "--name=aliaswithrepo", "--repos", + "https://dummyrepo", jar); try { add.doCall(); fail("Should have thrown exception"); @@ -376,9 +369,8 @@ void testAddWithRepos() throws IOException { @Test void testAddMissingScript() { - assertThrows(IllegalArgumentException.class, () -> { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("alias", "add", "--name=name"); - AliasAdd add = (AliasAdd) pr.subcommand().subcommand().commandSpec().userObject(); + assertThrows(ExitException.class, () -> { + dev.jbang.cli.Alias.AliasAdd add = JBang.parseCommand("alias", "add", "--name=name"); add.doCall(); }); } @@ -391,17 +383,14 @@ void testAddExisting() throws IOException { Path testFile2 = cwd.resolve("test2.java"); Files.write(testFile2, "// Test file 2".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - int exitCode = JBang.getCommandLine() - .execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + int exitCode = JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_OK)); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Alias alias = Alias.get("name"); assertThat(alias.scriptRef, is("test.java")); - exitCode = JBang.getCommandLine() - .execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile2.toString()); + exitCode = JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", testFile2.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_INVALID_INPUT)); - exitCode = JBang.getCommandLine() - .execute("alias", "add", "-f", cwd.toString(), "--name=name", "--force", testFile2.toString()); + exitCode = JBang.execute("alias", "add", "-f", cwd.toString(), "--name=name", "--force", testFile2.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_OK)); alias = Alias.get("name"); assertThat(alias.scriptRef, is("test2.java")); diff --git a/src/test/java/dev/jbang/cli/TestApp.java b/src/test/java/dev/jbang/cli/TestApp.java index b5d7072dd..0966215bc 100644 --- a/src/test/java/dev/jbang/cli/TestApp.java +++ b/src/test/java/dev/jbang/cli/TestApp.java @@ -24,8 +24,6 @@ import dev.jbang.source.ProjectBuilder; import dev.jbang.util.Util; -import picocli.CommandLine; - public class TestApp extends BaseTest { private static final List shContents = Arrays.asList("#!/bin/sh", "exec jbang run '$CWD/itests/with space/helloworld.java' \"$@\""); @@ -51,7 +49,7 @@ public class TestApp extends BaseTest { @Test void testAppInstallFile() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", src); + CaptureResult result = checkedRun("app", "install", "--no-build", src); assertThat(result.err, containsString("Command installed: helloworld")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -64,12 +62,10 @@ void testAppInstallFile() throws Exception { @Test void testAppInstallWithRepos() throws Exception { String src = examplesTestFolder.resolve("repos.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("app", "install", "--no-build", "--fresh", "--force", - "--repos=https://maven.repository.redhat.com/ga/", src); - AppInstall app = (AppInstall) pr.subcommand().subcommand().commandSpec().userObject(); + App.AppInstall app = JBang.parseCommand("app", "install", "--no-build", "--fresh", "--force", + "--repos=https://maven.repository.redhat.com/ga/", src); - ProjectBuilder pb = app.createProjectBuilder(); + ProjectBuilder pb = app.createBaseProjectBuilder(); Project prj = pb.build(src); assertThat(prj.getRepositories(), hasItem(new MavenRepo("central", "https://repo1.maven.org/maven2/"))); } @@ -77,7 +73,7 @@ void testAppInstallWithRepos() throws Exception { @Test void testAppNativeInstallFile() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "--native", src); + CaptureResult result = checkedRun("app", "install", "--no-build", "--native", src); assertThat(result.err, containsString("Command installed: helloworld")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -90,7 +86,7 @@ void testAppNativeInstallFile() throws Exception { @Test void testAppInstallExtensionLessFile() throws Exception { String src = examplesTestFolder.resolve("kubectl-example").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", src); + CaptureResult result = checkedRun("app", "install", "--no-build", src); assertThat(result.err, containsString("Command installed: kubectl-example")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -103,7 +99,7 @@ void testAppInstallExtensionLessFile() throws Exception { @Test @Timeout(value = 2, unit = TimeUnit.MINUTES) void testAppInstallURL() throws Exception { - CaptureResult result = checkedRun(null, "app", "install", "--no-build", + CaptureResult result = checkedRun("app", "install", "--no-build", "https://github.com/jbangdev/k8s-cli-java/blob/jbang/kubectl-example"); assertThat(result.err, containsString("Command installed: kubectl-example")); if (Util.isWindows()) { @@ -116,7 +112,7 @@ void testAppInstallURL() throws Exception { @Test void testAppInstallGVA() throws Exception { - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "--name", "h2", + CaptureResult result = checkedRun("app", "install", "--no-build", "--name", "h2", "com.h2database:h2:1.4.200"); assertThat(result.err, containsString("Command installed: h2")); if (Util.isWindows()) { @@ -138,14 +134,14 @@ void testAppInstallGVA() throws Exception { @Test void testAppInstallFileExists() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", src); + CaptureResult result = checkedRun("app", "install", "--no-build", src); assertThat(result.err, containsString("Command installed: helloworld")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); } else { assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); } - result = checkedRun(null, "app", "install", "--no-build", src); + result = checkedRun("app", "install", "--no-build", src); assertThat(result.err, containsString("A script with name 'helloworld' already exists, use '--force' to install anyway.")); } @@ -153,14 +149,14 @@ void testAppInstallFileExists() throws Exception { @Test void testAppInstallFileForce() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", src); + CaptureResult result = checkedRun("app", "install", "--no-build", src); assertThat(result.err, containsString("Command installed: helloworld")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); } else { assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); } - result = checkedRun(null, "app", "install", "--no-build", "--force", src); + result = checkedRun("app", "install", "--no-build", "--force", src); assertThat(result.err, containsString("Command installed: helloworld")); testScripts(); } @@ -199,7 +195,7 @@ private void testScript(String name, String cwd, List contents) throws I @Test void testAppInstallFileWithName() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "--name=hello", src); + CaptureResult result = checkedRun("app", "install", "--no-build", "--name=hello", src); assertThat(result.err, containsString("Command installed: hello")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -214,14 +210,14 @@ void testAppInstallFileWithName() throws Exception { @Test void testAppInstallFileWithNameExists() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "--name=hello", src); + CaptureResult result = checkedRun("app", "install", "--no-build", "--name=hello", src); assertThat(result.err, containsString("Command installed: hello")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); } else { assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); } - result = checkedRun(null, "app", "install", "--no-build", "--name=hello", src); + result = checkedRun("app", "install", "--no-build", "--name=hello", src); assertThat(result.err, containsString("A script with name 'hello' already exists, use '--force' to install anyway.")); } @@ -229,22 +225,22 @@ void testAppInstallFileWithNameExists() throws Exception { @Test void testAppInstallFileWithNameForce() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "--name=hello", src); + CaptureResult result = checkedRun("app", "install", "--no-build", "--name=hello", src); assertThat(result.err, containsString("Command installed: hello")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); } else { assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); } - result = checkedRun(null, "app", "install", "--no-build", "--force", "--name=hello", src); + result = checkedRun("app", "install", "--no-build", "--force", "--name=hello", src); assertThat(result.err, containsString("Command installed: hello")); } @Test void testAppInstallAlias() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - checkedRun(null, "alias", "add", "-g", "--name=apptest", src); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "apptest"); + checkedRun("alias", "add", "-g", "--name=apptest", src); + CaptureResult result = checkedRun("app", "install", "--no-build", "apptest"); assertThat(result.err, containsString("Command installed: apptest")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -259,10 +255,10 @@ void testAppInstallAlias() throws Exception { @Test void testAppInstallAliasFromRepo() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - checkedRun(null, "alias", "add", "-g", "--name=apptest", src); - checkedRun(null, "catalog", "add", "-g", "--name=testrepo", + checkedRun("alias", "add", "-g", "--name=apptest", src); + checkedRun("catalog", "add", "-g", "--name=testrepo", jbangTempDir.resolve("jbang-catalog.json").toString()); - CaptureResult result = checkedRun(null, "app", "install", "--no-build", "apptest@testrepo"); + CaptureResult result = checkedRun("app", "install", "--no-build", "apptest@testrepo"); assertThat(result.err, containsString("Command installed: apptest")); if (Util.isWindows()) { assertThat(result.result, equalTo(BaseCommand.EXIT_EXECUTE)); @@ -277,9 +273,9 @@ void testAppInstallAliasFromRepo() throws Exception { @Test void testAppInstallInvalidName() throws Exception { try { - checkedRun(null, "app", "install", "--no-build", "--name=invalid>name", "def/not/existing/file"); + checkedRun("app", "install", "--no-build", "--name=invalid>name", "def/not/existing/file"); Assert.fail(); - } catch (IllegalArgumentException e) { + } catch (ExitException e) { assertThat(e.getMessage(), containsString("Not a valid command name")); } } @@ -287,7 +283,7 @@ void testAppInstallInvalidName() throws Exception { @Test void testAppInstallInvalidRef() throws Exception { try { - checkedRun(null, "app", "install", "--no-build", "def/not/existing/file"); + checkedRun("app", "install", "--no-build", "def/not/existing/file"); Assert.fail(); } catch (ExitException e) { assertThat(e.getMessage(), @@ -298,10 +294,10 @@ void testAppInstallInvalidRef() throws Exception { @Test void testAppList() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - checkedRun(null, "app", "install", "--no-build", "--name=hello1", src); - checkedRun(null, "app", "install", "--no-build", "--name=hello2", src); - checkedRun(null, "app", "install", "--no-build", "--name=hello3", src); - CaptureResult result = checkedRun(null, "app", "list"); + checkedRun("app", "install", "--no-build", "--name=hello1", src); + checkedRun("app", "install", "--no-build", "--name=hello2", src); + checkedRun("app", "install", "--no-build", "--name=hello3", src); + CaptureResult result = checkedRun("app", "list"); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(result.normalizedOut(), equalTo("hello1\nhello2\nhello3\n")); } @@ -309,14 +305,14 @@ void testAppList() throws Exception { @Test void testAppUninstall() throws Exception { String src = examplesTestFolder.resolve("with space/helloworld.java").toString(); - checkedRun(null, "app", "install", "--no-build", src); + checkedRun("app", "install", "--no-build", src); if (Util.isWindows()) { assertThat(Settings.getConfigBinDir().resolve("helloworld.cmd").toFile(), anExistingFile()); assertThat(Settings.getConfigBinDir().resolve("helloworld.ps1").toFile(), anExistingFile()); } else { assertThat(Settings.getConfigBinDir().resolve("helloworld").toFile(), anExistingFile()); } - CaptureResult result = checkedRun(null, "app", "uninstall", "helloworld"); + CaptureResult result = checkedRun("app", "uninstall", "helloworld"); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(result.err, containsString("Command removed: helloworld")); assertThat(Settings.getConfigBinDir().resolve("helloworld").toFile(), not(anExistingFile())); @@ -326,7 +322,7 @@ void testAppUninstall() throws Exception { @Test void testAppUninstallUnknown() throws Exception { - CaptureResult result = checkedRun(null, "app", "uninstall", "hello"); + CaptureResult result = checkedRun("app", "uninstall", "hello"); assertThat(result.result, equalTo(BaseCommand.EXIT_INVALID_INPUT)); assertThat(result.err, containsString("Command not found: hello")); } diff --git a/src/test/java/dev/jbang/cli/TestArguments.java b/src/test/java/dev/jbang/cli/TestArguments.java index d39cf43da..2d1205e2f 100644 --- a/src/test/java/dev/jbang/cli/TestArguments.java +++ b/src/test/java/dev/jbang/cli/TestArguments.java @@ -8,44 +8,25 @@ import java.util.Arrays; import java.util.Collections; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import dev.jbang.BaseTest; import dev.jbang.Settings; -import picocli.CommandLine; - class TestArguments extends BaseTest { - private CommandLine cli; - - @BeforeEach - void setup() { - cli = JBang.getCommandLine(); - } - - @Test - public void testHelpSections() { - JBang.getCommandRenderer().validate(JBang.getCommandLine().getHelp(), true); - } - @Test public void testBasicArguments() { - CommandLine.ParseResult pr = cli.parseArgs("run", "-h", "--debug", "myfile.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-h", "--debug", "myfile.java"); - assert run.helpMixin.helpRequested; assertThat(run.runMixin.debugString, hasEntry("address", "4004")); assertThat(run.scriptMixin.scriptOrFile, is("myfile.java")); assertThat(run.userParams.size(), is(0)); - } @Test public void testDoubleDebug() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug", "test.java", "--debug", "wonka"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug", "test.java", "--debug", "wonka"); assertThat(run.runMixin.debugString, hasEntry("address", "4004")); @@ -53,36 +34,25 @@ public void testDoubleDebug() { assertThat(run.userParams, is(Arrays.asList("--debug", "wonka"))); } - /** - * @Test public void testInit() { cli.parseArgs("--init", "x.java", "y.java"); - * assertThat(main.script, is("x.java")); assertThat(main.params, - * is(Arrays.asList("x.java", "y.java"))); } - **/ - @Test public void testStdInWithHelpParam() { - CommandLine.ParseResult pr = cli.parseArgs("run", "-", "--help"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-", "--help"); assertThat(run.scriptMixin.scriptOrFile, is("-")); - assertThat(run.helpMixin.helpRequested, is(false)); assertThat(run.userParams, is(Collections.singletonList("--help"))); } @Test public void testScriptWithHelpParam() { - CommandLine.ParseResult pr = cli.parseArgs("run", "test.java", "-h"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "test.java", "-h"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); - assertThat(run.helpMixin.helpRequested, is(false)); assertThat(run.userParams, is(Collections.singletonList("-h"))); } @Test public void testDebugWithScript() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -90,8 +60,7 @@ public void testDebugWithScript() { @Test public void testDebugPort() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug=*:5000", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=*:5000", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -101,8 +70,7 @@ public void testDebugPort() { @Test public void testDebugPortHost() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug=somehost:5000", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=somehost:5000", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -112,8 +80,7 @@ public void testDebugPortHost() { @Test public void testDynamicPort() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug=5000?", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=5000?", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -123,8 +90,7 @@ public void testDynamicPort() { @Test public void testShortDynamicPort() { - CommandLine.ParseResult pr = cli.parseArgs("run", "-d=address=5000?", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-d=address=5000?", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -134,8 +100,7 @@ public void testShortDynamicPort() { @Test public void testDynamicHostPort() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug=host:5000?", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=host:5000?", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -145,8 +110,7 @@ public void testDynamicHostPort() { @Test public void testAddressDynamicHostPort() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug=host:5000", "--debug=suspend=n", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=host:5000", "--debug=suspend=n", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -157,8 +121,7 @@ public void testAddressDynamicHostPort() { @Test public void testDebugPortSeperateValue() { - CommandLine.ParseResult pr = cli.parseArgs("run", "--debug", "xyz.dk:5005", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug", "xyz.dk:5005", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); assertThat(run.runMixin.debugString, notNullValue()); @@ -167,8 +130,7 @@ public void testDebugPortSeperateValue() { @Test public void testSimpleScript() { - CommandLine.ParseResult pr = cli.parseArgs("run", "test.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "test.java"); assertThat(run.scriptMixin.scriptOrFile, is("test.java")); } @@ -179,7 +141,7 @@ public void testClearCache() { environmentVariables.set(Settings.JBANG_CACHE_DIR, dir.toString()); assertThat(Files.isDirectory(dir), is(true)); - cli.execute("cache", "clear", "--all"); + JBang.execute("cache", "clear", "--all"); assertThat(Files.isDirectory(dir.resolve("urls")), is(false)); assertThat(Files.isDirectory(dir.resolve("jars")), is(false)); diff --git a/src/test/java/dev/jbang/cli/TestCatalog.java b/src/test/java/dev/jbang/cli/TestCatalog.java index e9c981602..393eea8ee 100644 --- a/src/test/java/dev/jbang/cli/TestCatalog.java +++ b/src/test/java/dev/jbang/cli/TestCatalog.java @@ -60,7 +60,7 @@ void testAddSucceeded() throws IOException { @Test void testAddInvalidName() throws IOException { - JBang.getCommandLine().execute("catalog", "add", "--name=invalid!", "dummy"); + JBang.execute("catalog", "add", "--name=invalid!", "dummy"); } @Test @@ -79,7 +79,7 @@ void testGetFatJar() throws IOException { @Test void testUpdate() throws IOException { - JBang.getCommandLine().execute("catalog", "update"); + JBang.execute("catalog", "update"); } @Test diff --git a/src/test/java/dev/jbang/cli/TestConfig.java b/src/test/java/dev/jbang/cli/TestConfig.java index 41cca4032..d26e53548 100644 --- a/src/test/java/dev/jbang/cli/TestConfig.java +++ b/src/test/java/dev/jbang/cli/TestConfig.java @@ -14,11 +14,9 @@ import dev.jbang.BaseTest; import dev.jbang.Configuration; -import picocli.CommandLine; - public class TestConfig extends BaseTest { - private static final int SUCCESS_EXIT = CommandLine.ExitCode.OK; + private static final int SUCCESS_EXIT = BaseCommand.EXIT_OK; static final String testConfig = "" + "one=footop\n" + @@ -46,7 +44,7 @@ void initEach() throws IOException { @Test void testList() throws Exception { - CaptureResult result = checkedRun(null, "config", "list"); + CaptureResult result = checkedRun("config", "list"); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), equalTo("format = text\n" + @@ -61,14 +59,14 @@ void testList() throws Exception { @Test void testGetLocal() throws Exception { - CaptureResult result = checkedRun(null, "config", "get", "two"); + CaptureResult result = checkedRun("config", "get", "two"); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), equalTo("bar\n")); } @Test void testGetGlobal() throws Exception { - CaptureResult result = checkedRun(null, "config", "get", "run.debug"); + CaptureResult result = checkedRun("config", "get", "run.debug"); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), equalTo("4004\n")); } @@ -76,14 +74,14 @@ void testGetGlobal() throws Exception { @Test void testSetLocal() throws Exception { assertThat(Configuration.read(testConfigFile).keySet(), not(hasItem("mykey"))); - checkedRun(null, "config", "set", "mykey", "myvalue"); + checkedRun("config", "set", "mykey=myvalue"); assertThat(Configuration.read(testConfigFile).keySet(), hasItem("mykey")); } @Test void testSetGlobal() throws Exception { assertThat(Configuration.read(configFile).keySet(), not(hasItem("mykey"))); - checkedRun(null, "config", "set", "--global", "mykey", "myvalue"); + checkedRun("config", "set", "--global", "mykey=myvalue"); assertThat(Configuration.read(configFile).keySet(), hasItem("mykey")); } @@ -92,51 +90,28 @@ void testSetBuiltin() throws Exception { Files.deleteIfExists(testConfigFile); Files.deleteIfExists(testConfigFileSub); assertThat(Configuration.read(configFile).keySet(), not(hasItem("run.debug"))); - checkedRun(null, "config", "set", "run.debug", "42"); + checkedRun("config", "set", "run.debug=42"); assertThat(Configuration.read(configFile).keySet(), hasItem("run.debug")); } @Test void testUnsetLocal() throws Exception { assertThat(Configuration.read(testConfigFile).keySet(), hasItem("two")); - checkedRun(null, "config", "unset", "two"); + checkedRun("config", "unset", "two"); assertThat(Configuration.read(testConfigFile).keySet(), not(hasItem("two"))); } @Test void testUnsetGlobal() throws Exception { - checkedRun(null, "config", "set", "--global", "mykey", "myvalue"); + checkedRun("config", "set", "--global", "mykey=myvalue"); assertThat(Configuration.read(configFile).keySet(), hasItem("mykey")); - checkedRun(null, "config", "unset", "--global", "mykey"); + checkedRun("config", "unset", "--global", "mykey"); assertThat(Configuration.read(configFile).keySet(), not(hasItem("mykey"))); } @Test void testUnsetBuiltin() throws Exception { - CaptureResult result = checkedRun(null, "config", "unset", "run.debug"); + CaptureResult result = checkedRun("config", "unset", "run.debug"); assertThat(result.normalizedErr(), containsString("Cannot remove built-in option")); } - - @Test - void testCommandDefaultValueDefault() { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("app", "list"); - AppList app = (AppList) pr.subcommand().subcommand().commandSpec().userObject(); - assertThat(app.formatMixin.format, equalTo(FormatMixin.Format.text)); - } - - @Test - void testCommandDefaultValueSpecificOverride() { - Configuration.instance().put("app.list.format", "json"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("app", "list"); - AppList app = (AppList) pr.subcommand().subcommand().commandSpec().userObject(); - assertThat(app.formatMixin.format, equalTo(FormatMixin.Format.json)); - } - - @Test - void testCommandDefaultValueGlobalOverride() { - Configuration.instance().put("format", "json"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("app", "list"); - AppList app = (AppList) pr.subcommand().subcommand().commandSpec().userObject(); - assertThat(app.formatMixin.format, equalTo(FormatMixin.Format.json)); - } } diff --git a/src/test/java/dev/jbang/cli/TestDeps.java b/src/test/java/dev/jbang/cli/TestDeps.java index 0ea917ab9..eb9decb63 100644 --- a/src/test/java/dev/jbang/cli/TestDeps.java +++ b/src/test/java/dev/jbang/cli/TestDeps.java @@ -22,7 +22,7 @@ void testDepsAddToJavaFile(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Add a dependency - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependency was added @@ -46,11 +46,10 @@ void testDepsAddMultipleToJavaFile(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Add multiple dependencies - int result = JBang.getCommandLine() - .execute("deps", "add", - "info.picocli:picocli:4.6.3", - "com.fasterxml.jackson.core:jackson-core:2.15.2", - testFile.toString()); + int result = JBang.execute("deps", "add", + "info.picocli:picocli:4.6.3", + "com.fasterxml.jackson.core:jackson-core:2.15.2", + testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependencies were added @@ -67,7 +66,7 @@ void testDepsAddToJbangFile(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Add a dependency - int result = JBang.getCommandLine().execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); + int result = JBang.execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependency was added @@ -84,11 +83,10 @@ void testDepsAddMultipleToJbangFile(@TempDir Path outputDir) throws IOException Files.write(testFile, content.getBytes()); // Add multiple dependencies - int result = JBang.getCommandLine() - .execute("deps", "add", - "org.slf4j:slf4j-api:1.7.36", - "org.slf4j:slf4j-simple:1.7.36", - testFile.toString()); + int result = JBang.execute("deps", "add", + "org.slf4j:slf4j-api:1.7.36", + "org.slf4j:slf4j-simple:1.7.36", + testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependencies were added @@ -105,7 +103,7 @@ void testDepsAddDuplicateToJavaFile(@TempDir Path outputDir) throws IOException Files.write(testFile, content.getBytes()); // Try to add the same dependency - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependency was not duplicated @@ -122,7 +120,7 @@ void testDepsAddDuplicateToJbangFile(@TempDir Path outputDir) throws IOException Files.write(testFile, content.getBytes()); // Try to add the same dependency - int result = JBang.getCommandLine().execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); + int result = JBang.execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); assertThat(result).isEqualTo(0); String fileContent = Util.readString(testFile); @@ -138,7 +136,7 @@ void testDepsAddWithVersionUpdate(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Add the same dependency with different version - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isEqualTo(0); String fileContent = Util.readString(testFile); @@ -155,7 +153,7 @@ void testDepsAddToJavaFileWithExistingDeps(@TempDir Path outputDir) throws IOExc Files.write(testFile, content.getBytes()); // Add new dependency - int result = JBang.getCommandLine().execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); + int result = JBang.execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the new dependency was added @@ -173,7 +171,7 @@ void testAddNearExistingDeps(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Add new dependency - int result = JBang.getCommandLine().execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); + int result = JBang.execute("deps", "add", "org.slf4j:slf4j-api:1.7.36", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the new dependency was added @@ -196,7 +194,7 @@ void testDepsAddToJavaFileWithoutDirectives(@TempDir Path outputDir) throws IOEx Files.write(testFile, content.getBytes()); // Add a dependency - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isEqualTo(0); // Verify the dependency was added at the beginning @@ -213,7 +211,7 @@ void testDepsAddInvalidDependency(@TempDir Path outputDir) throws IOException { Files.write(testFile, content.getBytes()); // Try to add an invalid dependency - int result = JBang.getCommandLine().execute("deps", "add", "invalid-dependency", testFile.toString()); + int result = JBang.execute("deps", "add", "invalid-dependency", testFile.toString()); assertThat(result).isNotEqualTo(0); } @@ -221,7 +219,7 @@ void testDepsAddInvalidDependency(@TempDir Path outputDir) throws IOException { void testDepsAddToNonExistentFile(@TempDir Path outputDir) throws IOException { // Try to add dependency to non-existent file Path testFile = outputDir.resolve("nonexistent.java"); - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isNotEqualTo(0); } @@ -233,21 +231,21 @@ void testDepsAddToUnsupportedFileType(@TempDir Path outputDir) throws IOExceptio Files.write(testFile, content.getBytes()); // Try to add dependency to unsupported file - int result = JBang.getCommandLine().execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); + int result = JBang.execute("deps", "add", "info.picocli:picocli:4.6.3", testFile.toString()); assertThat(result).isNotEqualTo(0); } @Test void testDepsAddMissingParameters() { // Test with missing parameters - int result = JBang.getCommandLine().execute("deps", "add"); + int result = JBang.execute("deps", "add"); assertThat(result).isNotEqualTo(0); } @Test void testDepsAddOnlyTargetFile() { // Test with only target file (no dependencies) - int result = JBang.getCommandLine().execute("deps", "add", "test.java"); + int result = JBang.execute("deps", "add", "test.java"); assertThat(result).isNotEqualTo(0); } } diff --git a/src/test/java/dev/jbang/cli/TestEdit.java b/src/test/java/dev/jbang/cli/TestEdit.java index 227607698..1d09c5b94 100644 --- a/src/test/java/dev/jbang/cli/TestEdit.java +++ b/src/test/java/dev/jbang/cli/TestEdit.java @@ -29,8 +29,6 @@ import dev.jbang.source.ProjectBuilder; import dev.jbang.util.Util; -import picocli.CommandLine; - public class TestEdit extends BaseTest { StringWriter output; @@ -44,7 +42,7 @@ void setup() { void testEdit(@TempDir Path outputDir) throws IOException { String s = outputDir.resolve("edit.java").toString(); - JBang.getCommandLine().execute("init", s); + JBang.execute("init", s); assertThat(new File(s).exists(), is(true)); ProjectBuilder pb = Project.builder(); @@ -82,7 +80,7 @@ void testEditDeps(@TempDir Path outputDir) throws IOException { Path p = outputDir.resolve("edit.java"); String s = p.toString(); - JBang.getCommandLine().execute("--verbose", "init", s); + JBang.execute("--verbose", "init", s); assertThat(new File(s).exists(), is(true)); Util.writeString(p, "//DEPS org.openjfx:javafx-graphics:11.0.2${bougus:}\n" + Util.readString(p)); @@ -115,13 +113,13 @@ void testEditDepsNoJitpack(@TempDir Path outputDir) throws IOException { Path p = outputDir.resolve("edit.java"); String s = p.toString(); - JBang.getCommandLine().execute("init", s); + JBang.execute("init", s); assertThat(new File(s).exists(), is(true)); Util.writeString(p, "//DEPS com.github.lalyos:jfiglet:0.0.8\n" + Util.readString(p)); ProjectBuilder pb = Project.builder(); - Project prj = pb.build(s); + Project prj = pb.build(p.toString()); Path project = new Edit().createProjectForLinkedEdit(prj, Collections.emptyList(), false); @@ -148,13 +146,13 @@ void testEditJitPackDepAndRepo(@TempDir Path outputDir) throws IOException { Path p = outputDir.resolve("edit.java"); String s = p.toString(); - JBang.getCommandLine().execute("init", s); + JBang.execute("init", s); assertThat(new File(s).exists(), is(true)); Util.writeString(p, "//DEPS https://github.com/oldskoolsh/libvirt-schema/tree/0.0.2\n" + Util.readString(p)); ProjectBuilder pb = Project.builder(); - Project prj = pb.build(s); + Project prj = pb.build(p.toString()); Path project = new Edit().createProjectForLinkedEdit(prj, Collections.emptyList(), false); @@ -193,7 +191,7 @@ void testEditMultiSource(@TempDir Path outputDir) throws IOException { @Test void testEditNonJava(@TempDir Path outputDir) throws IOException { Path p = outputDir.resolve("kube-example"); - int result = JBang.getCommandLine().execute("init", p.toString()); + int result = JBang.execute("init", p.toString()); String s = p.toString(); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); @@ -233,19 +231,11 @@ void testEditFile(@TempDir Path outputDir) throws IOException { }); } - /* - * @Test void testEditMissingScript() { - * assertThrows(IllegalArgumentException.class, () -> { CommandLine.ParseResult - * pr = JBang.getCommandLine().parseArgs("edit"); Edit edit = (Edit) - * pr.subcommand().commandSpec().userObject(); edit.doCall(); }); } - */ - @Test void testSandboxEditNonSource() { assertThrows(ExitException.class, () -> { Path jar = examplesTestFolder.resolve("hellojar.jar"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "-b", "--no-open", jar.toString()); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); + Edit edit = JBang.parseCommand("edit", "-b", "--no-open", jar.toString()); edit.doCall(); }); } @@ -253,8 +243,7 @@ void testSandboxEditNonSource() { @Test void testSandboxEdit() throws IOException { Path src = examplesTestFolder.resolve("helloworld.java"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("edit", "-b", "--no-open", src.toString()); - Edit edit = (Edit) pr.subcommand().commandSpec().userObject(); + Edit edit = JBang.parseCommand("edit", "-b", "--no-open", src.toString()); edit.doCall(); } } diff --git a/src/test/java/dev/jbang/cli/TestExport.java b/src/test/java/dev/jbang/cli/TestExport.java index bf3f2d1bf..728d3b62f 100644 --- a/src/test/java/dev/jbang/cli/TestExport.java +++ b/src/test/java/dev/jbang/cli/TestExport.java @@ -24,15 +24,13 @@ import dev.jbang.BaseTest; import dev.jbang.util.Util; -import picocli.CommandLine; - public class TestExport extends BaseTest { @Test void testExportFile() throws Exception { String src = examplesTestFolder.resolve("helloworld.java").toString(); String outFile = cwdDir.resolve("subdir/helloworld.jar").toString(); - CaptureResult result = checkedRun(null, "export", "local", "-O", outFile, src); + CaptureResult result = checkedRun("export", "local", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*helloworld.jar.*")); assertThat(new File(outFile), anExistingFile()); } @@ -41,7 +39,7 @@ void testExportFile() throws Exception { void testExportFileName() throws Exception { String src = examplesTestFolder.resolve("helloworld.java").toString(); String outFile = cwdDir.resolve("helloworld").toString(); - CaptureResult result = checkedRun(null, "export", "local", "-O", outFile, src); + CaptureResult result = checkedRun("export", "local", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*helloworld.jar.*")); assertThat(new File(outFile + ".jar"), anExistingFile()); } @@ -50,7 +48,7 @@ void testExportFileName() throws Exception { void testExportNoFileName() throws Exception { String src = examplesTestFolder.resolve("helloworld.java").toString(); String outFile = cwdDir.resolve("helloworld.jar").toString(); - CaptureResult result = checkedRun(null, "export", "local", src); + CaptureResult result = checkedRun("export", "local", src); assertThat(result.err, matchesPattern("(?s).*Exported to.*helloworld.jar.*")); assertThat(new File(outFile), anExistingFile()); } @@ -59,21 +57,21 @@ void testExportNoFileName() throws Exception { void testExportPortableNoclasspath() throws Exception { String src = examplesTestFolder.resolve("helloworld.java").toString(); String outFile = cwdDir.resolve("helloworld.jar").toString(); - CaptureResult result = checkedRun(null, "export", "portable", "-O", outFile, src); + CaptureResult result = checkedRun("export", "portable", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*helloworld.jar.*")); assertThat(new File(outFile), anExistingFile()); - assertThat(cwdDir.resolve(ExportPortable.LIB).toFile(), not(anExistingFileOrDirectory())); + assertThat(cwdDir.resolve(Export.ExportPortable.LIB).toFile(), not(anExistingFileOrDirectory())); } @Test void testExportPortableWithClasspath() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); String outFile = cwdDir.resolve("classpath_log.jar").toString(); - CaptureResult result = checkedRun(null, "export", "portable", "-O", outFile, src); + CaptureResult result = checkedRun("export", "portable", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*classpath_log.jar.*")); assertThat(new File(outFile), anExistingFile()); - assertThat(cwdDir.resolve(ExportPortable.LIB).toFile(), anExistingDirectory()); - assertThat(cwdDir.resolve(ExportPortable.LIB).toFile().listFiles().length, Matchers.equalTo(1)); + assertThat(cwdDir.resolve(Export.ExportPortable.LIB).toFile(), anExistingDirectory()); + assertThat(cwdDir.resolve(Export.ExportPortable.LIB).toFile().listFiles().length, Matchers.equalTo(1)); File jar = new File(outFile); @@ -91,10 +89,10 @@ void testExportPortableWithClasspath() throws Exception { void testExportWithClasspath() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); String outFile = cwdDir.resolve("classpath_log.jar").toString(); - CaptureResult result = checkedRun(null, "export", "local", "-O", outFile, src); + CaptureResult result = checkedRun("export", "local", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*classpath_log.jar.*")); assertThat(new File(outFile), anExistingFile()); - assertThat(cwdDir.resolve(ExportPortable.LIB).toFile(), not(anExistingDirectory())); + assertThat(cwdDir.resolve(Export.ExportPortable.LIB).toFile(), not(anExistingDirectory())); File jar = new File(outFile); @@ -112,7 +110,7 @@ void testExportWithClasspath() throws Exception { void testExportMavenPublishNoclasspath() throws Exception { File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "mavenrepo", "-O", outFile.toString(), + CaptureResult result = checkedRun("export", "mavenrepo", "-O", outFile.toString(), "--group=my.thing.right", examplesTestFolder.resolve("helloworld.java").toString()); assertThat(result.err, matchesPattern("(?s).*Exported to.*target.*")); assertThat( @@ -128,7 +126,7 @@ void testExportMavenPublishNoclasspath() throws Exception { void testExportMavenPublishNoOutputdir() throws Exception { File outFile = jbangTempDir.resolve("target").toFile(); // outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "mavenrepo", "-O", outFile.toString(), + CaptureResult result = checkedRun("export", "mavenrepo", "-O", outFile.toString(), "--group=my.thing.right", examplesTestFolder.resolve("helloworld.java").toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_INVALID_INPUT)); @@ -138,7 +136,7 @@ void testExportMavenPublishNoOutputdir() throws Exception { void testExportMavenPublishNoGroup() throws Exception { File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "mavenrepo", "--force", "-O", + CaptureResult result = checkedRun("export", "mavenrepo", "--force", "-O", outFile.toString(), examplesTestFolder.resolve("helloworld.java").toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_INVALID_INPUT)); assertThat(result.err, containsString("Add --group= and run again")); @@ -147,7 +145,7 @@ void testExportMavenPublishNoGroup() throws Exception { @Test void testExportMavenPublishWithClasspath() throws Exception { Path outFile = mavenTempDir; - CaptureResult result = checkedRun(null, "export", "mavenrepo", "--force", + CaptureResult result = checkedRun("export", "mavenrepo", "--force", "--group=g.a.v", examplesTestFolder.resolve("classpath_log.java").toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(outFile.resolve("g/a/v/classpath_log/999-SNAPSHOT/classpath_log-999-SNAPSHOT.jar").toFile(), @@ -166,7 +164,7 @@ void testExportMavenPublishWithClasspath() throws Exception { void testExportMavenPublishWithGAV() throws Exception { File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "mavenrepo", "-O", outFile.toString(), + CaptureResult result = checkedRun("export", "mavenrepo", "-O", outFile.toString(), examplesTestFolder.resolve("quote.java").toString()); assertThat(result.err, matchesPattern("(?s).*Exported to.*target.*")); assertThat( @@ -180,9 +178,8 @@ void testExportMavenPublishWithGAV() throws Exception { @Test void testExportMissingScript() { - assertThrows(IllegalArgumentException.class, () -> { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("export", "local"); - ExportLocal export = (ExportLocal) pr.subcommand().subcommand().commandSpec().userObject(); + assertThrows(ExitException.class, () -> { + Export.ExportLocal export = JBang.parseCommand("export", "local"); export.doCall(); }); } @@ -191,7 +188,7 @@ void testExportMissingScript() { void testExportFatJar() throws Exception { String src = examplesTestFolder.resolve("helloworld.java").toString(); String outFile = cwdDir.resolve("subdir/helloworld.jar").toString(); - CaptureResult result = checkedRun(null, "export", "fatjar", "-O", outFile, src); + CaptureResult result = checkedRun("export", "fatjar", "-O", outFile, src); assertThat(result.err, matchesPattern("(?s).*Exported to.*helloworld.jar.*")); assertThat(new File(outFile), anExistingFile()); } @@ -201,7 +198,7 @@ void testExportGradleProjectFromJava() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/java/classpath_log.java"); @@ -220,7 +217,7 @@ void testExportGradleProjectFromGroovy() throws Exception { String src = examplesTestFolder.resolve("classpath_log.groovy").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/groovy/classpath_log.groovy"); @@ -239,7 +236,7 @@ void testExportGradleProjectFromKotlin1() throws Exception { String src = examplesTestFolder.resolve("classpath_log.kt").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/kotlin/classpath_log.kt"); @@ -258,7 +255,7 @@ void testExportGradleProjectFromKotlin2() throws Exception { String src = examplesTestFolder.resolve("classpath_main.kt").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/kotlin/classpath_main.kt"); @@ -276,7 +273,7 @@ void testExportGradleProjectWithGAV() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), "-g", "dev.jbang.test", "-a", "app", "-v", "1.2.3", src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() @@ -297,7 +294,7 @@ void testExportGradleProjectWithBOM() throws Exception { String src = examplesTestFolder.resolve("classpath_log_bom.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve( @@ -320,7 +317,7 @@ void testExportGradleProjectWithTags() throws Exception { String src = examplesTestFolder.resolve("exporttags.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "gradle", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "gradle", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() @@ -371,7 +368,7 @@ void testExportMavenProject() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "maven", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "maven", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/java/classpath_log.java"); @@ -401,7 +398,7 @@ void testExportMavenProjectWithPackages() throws Exception { String src = examplesTestFolder.resolve("RootOne.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "maven", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "maven", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve("src/main/java/RootOne.java"); @@ -426,7 +423,7 @@ void testExportMavenProjectWithGAV() throws Exception { String src = examplesTestFolder.resolve("classpath_log.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "maven", "--force", "-O", outFile.toString(), + CaptureResult result = checkedRun("export", "maven", "--force", "-O", outFile.toString(), "-g", "dev.jbang.test", "-a", "app", "-v", "1.2.3", src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() @@ -457,7 +454,7 @@ void testExportMavenProjectWithBOM() throws Exception { String src = examplesTestFolder.resolve("classpath_log_bom.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "maven", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "maven", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() .resolve( @@ -491,7 +488,7 @@ void testExportMavenProjectWithTags() throws Exception { String src = examplesTestFolder.resolve("exporttags.java").toString(); File outFile = jbangTempDir.resolve("target").toFile(); outFile.mkdirs(); - CaptureResult result = checkedRun(null, "export", "maven", "--force", "-O", outFile.toString(), src); + CaptureResult result = checkedRun("export", "maven", "--force", "-O", outFile.toString(), src); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); Path targetSrcPath = outFile.toPath() @@ -555,7 +552,7 @@ void testExportFatjarFileDirConflict1(@TempDir Path temp) throws Exception { String code = "//DEPS src1.java src2.java src3.java src4.java\n"; Path src = temp.resolve("test.java"); Util.writeString(src, code); - CaptureResult result = checkedRun(null, "export", "fatjar", src.toString()); + CaptureResult result = checkedRun("export", "fatjar", src.toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(result.err, containsString("[WARN] Skipping conflicting duplicate file vs directory:")); } @@ -570,7 +567,7 @@ void testExportFatjarFileDirConflict2(@TempDir Path temp) throws Exception { String code = "//DEPS src2.java src3.java src4.java src1.java\n"; Path src = temp.resolve("test.java"); Util.writeString(src, code); - CaptureResult result = checkedRun(null, "export", "fatjar", src.toString()); + CaptureResult result = checkedRun("export", "fatjar", src.toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(result.err, containsString("[WARN] Skipping conflicting duplicate file vs directory:")); } @@ -586,7 +583,7 @@ void testExportFatjarSignatures(@TempDir Path temp) throws Exception { String code2 = "//DEPS src.java\n"; Path src = temp.resolve("test.java"); Util.writeString(src, code2); - CaptureResult result = checkedRun(null, "--verbose", "export", "fatjar", src.toString()); + CaptureResult result = checkedRun("--verbose", "export", "fatjar", src.toString()); assertThat(result.result, equalTo(BaseCommand.EXIT_OK)); assertThat(result.err, containsString("Removing signature file:")); assertThat(result.err, containsString("DUMMY.SF")); diff --git a/src/test/java/dev/jbang/cli/TestExternalDeps.java b/src/test/java/dev/jbang/cli/TestExternalDeps.java index 0dce9db9c..00fdbfefb 100644 --- a/src/test/java/dev/jbang/cli/TestExternalDeps.java +++ b/src/test/java/dev/jbang/cli/TestExternalDeps.java @@ -14,8 +14,6 @@ import dev.jbang.source.ProjectBuilder; import dev.jbang.util.Util; -import picocli.CommandLine; - class TestExternalDeps extends BaseTest { String checkdeps = "public class test {" @@ -41,10 +39,8 @@ void testDepsWork() throws IOException { Util.writeString(f.toPath(), checkdeps); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--deps", "info.picocli:picocli:4.6.3", - f.getPath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--deps", "info.picocli:picocli:4.6.3", + f.getPath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getPath()); @@ -59,13 +55,11 @@ void testReposWork() throws IOException { Util.writeString(f.toPath(), checkdeps); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--repos", "central", "--repos", - "https://jitpack.io", - "--deps", - "com.github.jbangdev.jbang-resolver:shrinkwrap-resolver-api:3.1.5-allowpom", - f.getPath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--repos", "central", "--repos", + "https://jitpack.io", + "--deps", + "com.github.jbangdev.jbang-resolver:shrinkwrap-resolver-api:3.1.5-allowpom", + f.getPath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getPath()); @@ -74,4 +68,4 @@ void testReposWork() throws IOException { assertThat(result, matchesPattern(".*com[/\\\\]github[/\\\\]jbangdev[/\\\\]jbang.*")); } -} \ No newline at end of file +} diff --git a/src/test/java/dev/jbang/cli/TestInfo.java b/src/test/java/dev/jbang/cli/TestInfo.java index 09d8f77e7..448955d14 100644 --- a/src/test/java/dev/jbang/cli/TestInfo.java +++ b/src/test/java/dev/jbang/cli/TestInfo.java @@ -16,16 +16,13 @@ import dev.jbang.BaseTest; import dev.jbang.util.WarTestFixtures; -import picocli.CommandLine; - public class TestInfo extends BaseTest { @Test void testInfoToolsSimple() { String src = examplesTestFolder.resolve("quote.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, allOf( containsString("quote.java."), @@ -43,10 +40,9 @@ void testInfoToolsSimple() { @Test void testInfoToolsBuilt() { String src = examplesTestFolder.resolve("quote.java").toString(); - JBang.getCommandLine().execute("build", src); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + JBang.execute("build", src); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, allOf( containsString("quote.java."), @@ -60,13 +56,11 @@ void testInfoToolsBuilt() { @Test void testInfoToolsWithDeps() { String src = examplesTestFolder.resolve("helloworld.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("info", "tools", - "--deps", "info.picocli:picocli:4.6.3,commons-io:commons-io:2.8.0", - "--deps", "org.apache.commons:commons-lang3:3.12.0", - src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", + "--deps", "info.picocli:picocli:4.6.3,commons-io:commons-io:2.8.0", + "--deps", "org.apache.commons:commons-lang3:3.12.0", + src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, allOf( containsString("helloworld.java."), @@ -85,9 +79,8 @@ void testInfoToolsWithDeps() { void testInfoToolsWithClasspath() { String src = examplesTestFolder.resolve("helloworld.java").toString(); String jar = examplesTestFolder.resolve("hellojar.jar").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", "--cp", jar, src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", "--cp", jar, src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, allOf( containsString("helloworld.java."), @@ -104,9 +97,8 @@ void testInfoToolsWithClasspath() { void testInfoClasspathNested() { String src = examplesTestFolder.resolve("sources.java").toString(); String quote = examplesTestFolder.resolve("quote.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, allOf( containsString("sources.java."), @@ -127,9 +119,8 @@ void testInfoClasspathNested() { @Test void testInfoJShell() { String src = examplesTestFolder.resolve("basic.jsh").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); assertThat(info.applicationJar, equalTo(null)); assertThat(info.backingResource, equalTo(src)); @@ -143,9 +134,8 @@ void testInfoJShell() { @Test void testInfoHelloJar() { String jar = examplesTestFolder.resolve("hellojar.jar").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", jar); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", jar); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(jar)); assertThat(info.applicationJar, equalTo(jar)); assertThat(info.backingResource, equalTo(jar)); @@ -157,49 +147,41 @@ void testInfoHelloJar() { @Test void testInfoStarSources() { String src = examplesTestFolder.resolve("sources/ying.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(src)); - // assertThat(info.applicationJar, equalTo(src)); assertThat(info.backingResource, equalTo(src)); - // assertThat(info.javaVersion, not(nullValue())); - // assertThat(info.mainClass, equalTo("helloworld")); assertThat(info.resolvedDependencies, empty()); } @Test void testInfoDocsFile() { String src = examplesTestFolder.resolve("docstest1.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "docs", src); - Docs docs = (Docs) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ProjectFile pf = docs.getInfo(false).docs.get("main").get(0); + Info.Docs docs = JBang.parseCommand("info", "docs", src); + Info.BaseInfoCommand.ProjectFile pf = docs.getInfo(false).docs.get("main").get(0); assertThat(pf.originalResource, endsWith(File.separator + "itests" + File.separator + "readme.md")); } @Test void testInfoDocsUrl() { String src = examplesTestFolder.resolve("docstest2.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "docs", src); - Docs docs = (Docs) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ProjectFile pf = docs.getInfo(false).docs.get("main").get(0); + Info.Docs docs = JBang.parseCommand("info", "docs", src); + Info.BaseInfoCommand.ProjectFile pf = docs.getInfo(false).docs.get("main").get(0); assertThat(pf.originalResource, equalTo("https://www.jbang.dev/documentation/guide/latest/faq.html")); } @Test void givenScriptWithoutDocsDirectiveWhenInfoDocsCommandIsInvokedThenReturnEmptyResult() { String src = examplesTestFolder.resolve("docstest_nodocs.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "docs", src); - Docs docs = (Docs) pr.subcommand().subcommand().commandSpec().userObject(); + Info.Docs docs = JBang.parseCommand("info", "docs", src); assertThat(docs.getInfo(false).docs.isEmpty(), is(true)); } @Test void testInfoToolsWithDocs() throws IOException { String src = examplesTestFolder.resolve("docsexample.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", src); - Tools docs = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - docs.call(); + Info.Tools tools = JBang.parseCommand("info", "tools", src); + tools.doCall(); } @Test @@ -208,9 +190,8 @@ void testInfoClasspathForWar() throws IOException { try { WarTestFixtures.createExecutableWar(warPath, "TestMain"); String war = warPath.toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("info", "tools", war); - Tools tools = (Tools) pr.subcommand().subcommand().commandSpec().userObject(); - BaseInfoCommand.ScriptInfo info = tools.getInfo(false); + Info.Tools tools = JBang.parseCommand("info", "tools", war); + Info.BaseInfoCommand.ScriptInfo info = tools.getInfo(false); assertThat(info.originalResource, equalTo(war)); assertThat(info.applicationJar, equalTo(war)); assertThat(info.backingResource, equalTo(war)); diff --git a/src/test/java/dev/jbang/cli/TestInit.java b/src/test/java/dev/jbang/cli/TestInit.java index ccf640db9..00b35c8c6 100644 --- a/src/test/java/dev/jbang/cli/TestInit.java +++ b/src/test/java/dev/jbang/cli/TestInit.java @@ -68,7 +68,7 @@ void testInvalidInit(String filename) { void testCli(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("edit.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", "--verbose", "--template=cli", s); + int result = JBang.execute("init", "--verbose", "--template=cli", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); MatcherAssert.assertThat(Util.readString(x), Matchers.containsString("picocli")); @@ -78,14 +78,14 @@ void testCli(@TempDir Path outputDir) throws IOException { void testOldInit(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("nonexist.java"); String s = x.toString(); - assertEquals(JBang.getCommandLine().execute("--init", s), 2); + assertEquals(JBang.execute("--init", s), 2); } @Test void testMissingTemplate(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("edit.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", "--template=bogus", s); + int result = JBang.execute("init", "--template=bogus", s); assertThat(result, not(0)); assertThat(new File(s).exists(), is(false)); } @@ -94,7 +94,7 @@ void testMissingTemplate(@TempDir Path outputDir) throws IOException { void testDepsInit(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("edit.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", "--deps", "a.b.c:mydep:1.0", s); + int result = JBang.execute("init", "--deps", "a.b.c:mydep:1.0", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("//DEPS a.b.c:mydep:1.0")); @@ -104,7 +104,7 @@ void testDepsInit(@TempDir Path outputDir) throws IOException { void testJava25Plus(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("java25.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", "--java", "25", s); + int result = JBang.execute("init", "--java", "25", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("\nvoid main(String... args)")); @@ -114,7 +114,7 @@ void testJava25Plus(@TempDir Path outputDir) throws IOException { void testMultiDepsInit(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("edit.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", "--deps", "a.b.c:mydep:1.0", "--deps", "q.z:rrr:2.0", s); + int result = JBang.execute("init", "--deps", "a.b.c:mydep:1.0", "--deps", "q.z:rrr:2.0", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("//DEPS a.b.c:mydep:1.0")); @@ -125,12 +125,11 @@ void testMultiDepsInit(@TempDir Path outputDir) throws IOException { void testMultiDepsInitUsingCommas(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("sqlline.java"); String s = x.toString(); - int result = JBang.getCommandLine() - .execute( - "init", - "--deps", "org.hsqldb:hsqldb:2.5.0,net.hydromatic:foodmart-data-hsqldb:0.4", - "--deps", "org.another.company:dep:0.1", - s); + int result = JBang.execute( + "init", + "--deps", "org.hsqldb:hsqldb:2.5.0,net.hydromatic:foodmart-data-hsqldb:0.4", + "--deps", "org.another.company:dep:0.1", + s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); final String fileContent = Util.readString(x); @@ -143,7 +142,7 @@ void testMultiDepsInitUsingCommas(@TempDir Path outputDir) throws IOException { void testDefaultInit(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("edit.java"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", s); + int result = JBang.execute("init", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("class edit")); @@ -153,7 +152,7 @@ void testDefaultInit(@TempDir Path outputDir) throws IOException { void testInitExtensionlessKebab(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("xyz-plug"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", s); + int result = JBang.execute("init", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("class XyzPlug")); @@ -163,7 +162,7 @@ void testInitExtensionlessKebab(@TempDir Path outputDir) throws IOException { void testInitExtensionless(@TempDir Path outputDir) throws IOException { Path x = outputDir.resolve("xyzplug"); String s = x.toString(); - int result = JBang.getCommandLine().execute("init", s); + int result = JBang.execute("init", s); assertThat(result, is(0)); assertThat(new File(s).exists(), is(true)); assertThat(Util.readString(x), Matchers.containsString("class xyzplug")); @@ -171,9 +170,9 @@ void testInitExtensionless(@TempDir Path outputDir) throws IOException { @Test void testCatalog() throws IOException { -// int result = JBang.getCommandLine().execute("run", "jget@quintesse"); -// int result = JBang.getCommandLine().execute("catalog", "update"); - int result = JBang.getCommandLine().execute("catalog", "list", "quintesse"); +// int result = JBang.execute("run", "jget@quintesse"); +// int result = JBang.execute("catalog", "update"); + int result = JBang.execute("catalog", "list", "quintesse"); assertThat(result, is(0)); } @@ -209,7 +208,7 @@ void testInitMultipleFilesWrongName() throws IOException { void testInitMultipleFiles(String targetName, String initName, String outName, boolean abs) throws IOException { Path outFile = setupInitMultipleFiles(targetName, initName, abs); - int result = JBang.getCommandLine().execute("init", "-t=name", outFile.toString()); + int result = JBang.execute("init", "-t=name", outFile.toString()); assertThat(result, is(0)); assertThat(outFile.resolveSibling(outName).toFile(), aReadableFile()); Path f2 = outFile.resolveSibling("file2.java"); @@ -223,7 +222,7 @@ void testInitMultipleFiles(String targetName, String initName, String outName, b void testFailMultipleFiles(String targetName, String initName, String outName, boolean abs, int expectedResult) throws IOException { Path outFile = setupInitMultipleFiles(targetName, initName, abs); - int result = JBang.getCommandLine().execute("init", "-t=name", outFile.toString()); + int result = JBang.execute("init", "-t=name", outFile.toString()); assertThat(result, is(expectedResult)); } @@ -235,14 +234,12 @@ Path setupInitMultipleFiles(String targetName, String initName, boolean abs) thr Util.writeString(f2, "// {baseName} with {scriptref}"); Path f3 = Files.createFile(tplDir.resolve("file3.md")); if (abs) { - int addResult = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - targetName + "=" + f1.toString(), f2.toString(), f3.toString()); + int addResult = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + targetName + "=" + f1.toString(), f2.toString(), f3.toString()); assertThat(addResult, is(0)); } else { - int addResult = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - targetName + "=tpl/file1.java", "tpl/file2.java.qute", "tpl/file3.md"); + int addResult = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + targetName + "=tpl/file1.java", "tpl/file2.java.qute", "tpl/file3.md"); assertThat(addResult, is(0)); } Path appDir = Files.createDirectory(cwd.resolve("app")); @@ -274,15 +271,13 @@ void testInitProperties() throws IOException { Path f1 = Files.write(cwd.resolve("file1.java.qute"), "{prop1}{prop2}".getBytes()); Path out = cwd.resolve("result.java"); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - "{filename}" + "=" + f1.toAbsolutePath().toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + "{filename}" + "=" + f1.toAbsolutePath().toString()); assertThat(out.toFile().exists(), not(true)); - int result = JBang.getCommandLine() - .execute("init", "--verbose", "--template=name", "-Dprop1=propvalue", "-Dprop2=rocks", - out.toAbsolutePath().toString()); + int result = JBang.execute("init", "--verbose", "--template=name", "-Dprop1=propvalue", "-Dprop2=rocks", + out.toAbsolutePath().toString()); assertThat(result, is(0)); assertThat(out.toFile().exists(), is(true)); @@ -298,15 +293,13 @@ void testInitPropertiesWithDefaults() throws IOException { Path f1 = Files.write(cwd.resolve("file1.java.qute"), "{prop1}".getBytes()); Path out = cwd.resolve("result.java"); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::my-test-default-value", - "{filename}" + "=" + f1.toAbsolutePath().toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::my-test-default-value", + "{filename}" + "=" + f1.toAbsolutePath().toString()); assertThat(out.toFile().exists(), not(true)); - int result = JBang.getCommandLine() - .execute("init", "--verbose", "--template=name", - out.toAbsolutePath().toString()); + int result = JBang.execute("init", "--verbose", "--template=name", + out.toAbsolutePath().toString()); assertThat(result, is(0)); assertThat(out.toFile().exists(), is(true)); @@ -322,15 +315,13 @@ void testInitPropertiesWithPropertyWithoutDefaultValue() throws IOException { Path f1 = Files.write(cwd.resolve("file1.java.qute"), "{prop1}".getBytes()); Path out = cwd.resolve("result.java"); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::", - "{filename}" + "=" + f1.toAbsolutePath().toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::", + "{filename}" + "=" + f1.toAbsolutePath().toString()); assertThat(out.toFile().exists(), not(true)); - int result = JBang.getCommandLine() - .execute("init", "--verbose", "--template=name", - out.toAbsolutePath().toString()); + int result = JBang.execute("init", "--verbose", "--template=name", + out.toAbsolutePath().toString()); assertThat(result, is(0)); assertThat(out.toFile().exists(), is(true)); @@ -346,9 +337,8 @@ void testInitPropertiesIgnoringPropertyDefaults() throws IOException { Path f1 = Files.write(cwd.resolve("file1.java.qute"), "{prop1}".getBytes()); Path out = cwd.resolve("result.java"); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::my-test-default-value", - "{filename}" + "=" + f1.toAbsolutePath().toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", "-P=prop1::my-test-default-value", + "{filename}" + "=" + f1.toAbsolutePath().toString()); assertThat(out.toFile().exists(), not(true)); diff --git a/src/test/java/dev/jbang/cli/TestJdk.java b/src/test/java/dev/jbang/cli/TestJdk.java index 7a0af206e..d7c0f7365 100644 --- a/src/test/java/dev/jbang/cli/TestJdk.java +++ b/src/test/java/dev/jbang/cli/TestJdk.java @@ -13,7 +13,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,20 +25,26 @@ import dev.jbang.Settings; import dev.jbang.util.Util; -import picocli.CommandLine; - class TestJdk extends BaseTest { - private static final int SUCCESS_EXIT = CommandLine.ExitCode.OK; + private static final int SUCCESS_EXIT = BaseCommand.EXIT_OK; + private static final String[] DEFAULT_PROVIDERS = { "--jdk-providers", "default,jbang,linked" }; @BeforeEach void initJdk() { environmentVariables.clear(Settings.JBANG_CACHE_DIR + "_JDKS"); } + private String[] withProviders(String... args) { + String[] result = new String[args.length + DEFAULT_PROVIDERS.length]; + System.arraycopy(args, 0, result, 0, args.length); + System.arraycopy(DEFAULT_PROVIDERS, 0, result, args.length, DEFAULT_PROVIDERS.length); + return result; + } + @Test void testNoJdksInstalled() throws Exception { - CaptureResult result = checkedRun(jdk -> jdk.list(false, false, FormatMixin.Format.text)); + CaptureResult result = checkedRun(withProviders("jdk", "list")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), equalTo("No JDKs installed\n")); @@ -49,7 +54,7 @@ void testNoJdksInstalled() throws Exception { void testHasJdksInstalled() throws Exception { Arrays.asList(11, 12, 13).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.list(false, false, FormatMixin.Format.text)); + CaptureResult result = checkedRun(withProviders("jdk", "list")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), @@ -65,8 +70,8 @@ void testHasJdksInstalledWithJavaHome() throws Exception { initMockJdkDir(jdkPath, "13.0.7"); environmentVariables.set("JAVA_HOME", jdkPath.toString()); - CaptureResult result = checkedRun((Jdk jdk) -> jdk.list(false, false, FormatMixin.Format.text), - "jdk", "--jdk-providers", "default,javahome,jbang"); + CaptureResult result = checkedRun( + "jdk", "list", "--jdk-providers", "default,javahome,jbang"); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), @@ -75,7 +80,7 @@ void testHasJdksInstalledWithJavaHome() throws Exception { @Test void testJdksAvailable() throws Exception { - CaptureResult result = checkedRun(jdk -> jdk.list(true, false, FormatMixin.Format.text)); + CaptureResult result = checkedRun(withProviders("jdk", "list", "--available")); assertThat(result.result, equalTo(SUCCESS_EXIT)); Pattern p = Pattern.compile("^ {3}\\d+ \\(.+?\\)$", Pattern.MULTILINE); Matcher m = p.matcher(result.normalizedOut()); @@ -86,12 +91,12 @@ void testJdksAvailable() throws Exception { void testDefault() throws Exception { Arrays.asList(11, 12, 13).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.defaultJdk("12")); + CaptureResult result = checkedRun(withProviders("jdk", "default", "12")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), startsWith("[jbang] Default JDK set to 12")); - result = checkedRun(jdk -> jdk.defaultJdk(null)); + result = checkedRun(withProviders("jdk", "default")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), equalTo("[jbang] Default JDK is currently set to 12\n")); @@ -101,12 +106,12 @@ void testDefault() throws Exception { void testDefaultPlus() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.defaultJdk("16+")); + CaptureResult result = checkedRun(withProviders("jdk", "default", "16+")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), startsWith("[jbang] Default JDK set to 17")); - result = checkedRun(jdk -> jdk.defaultJdk(null)); + result = checkedRun(withProviders("jdk", "default")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), equalTo("[jbang] Default JDK is currently set to 17\n")); @@ -116,7 +121,7 @@ void testDefaultPlus() throws Exception { void testHome() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.home(null)); + CaptureResult result = checkedRun(withProviders("jdk", "home")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), endsWith(File.separator + "currentjdk\n")); @@ -126,7 +131,7 @@ void testHome() throws Exception { void testHomeDefault() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.home("default")); + CaptureResult result = checkedRun(withProviders("jdk", "home", "default")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), endsWith(File.separator + "currentjdk\n")); @@ -136,7 +141,7 @@ void testHomeDefault() throws Exception { void testHomeWithVersion() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.home("17")); + CaptureResult result = checkedRun(withProviders("jdk", "home", "17")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), endsWith("cache" + File.separator + "jdks" + File.separator + "17\n")); @@ -146,7 +151,7 @@ void testHomeWithVersion() throws Exception { void testHomePlus() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.home("16+")); + CaptureResult result = checkedRun(withProviders("jdk", "home", "16+")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), endsWith("cache" + File.separator + "jdks" + File.separator + "17\n")); @@ -156,17 +161,15 @@ void testHomePlus() throws Exception { void testJavaEnv() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv(null)); + CaptureResult result = checkedRun(withProviders("jdk", "java-env")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString(File.separator + "currentjdk" + File.separator + "bin" + File.pathSeparator)); if (Util.isWindows()) { - // By default, on Windows we only test with CMD, so let's retest - // pretending we're running from PowerShell environmentVariables.set(Util.JBANG_RUNTIME_SHELL, "powershell"); - result = checkedRun(jdk -> jdk.javaEnv(null)); + result = checkedRun(withProviders("jdk", "java-env")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), @@ -178,7 +181,7 @@ void testJavaEnv() throws Exception { void testJavaEnvDefault() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv("default")); + CaptureResult result = checkedRun(withProviders("jdk", "java-env", "default")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString(File.separator + "currentjdk")); @@ -188,7 +191,7 @@ void testJavaEnvDefault() throws Exception { void testJavaEnvWithVersion() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv("17")); + CaptureResult result = checkedRun(withProviders("jdk", "java-env", "17")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString("cache" + File.separator + "jdks" + File.separator + "17")); @@ -198,7 +201,7 @@ void testJavaEnvWithVersion() throws Exception { void testJavaEnvWithDefaultVersion() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv("11")); + CaptureResult result = checkedRun(withProviders("jdk", "java-env", "11")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString("cache" + File.separator + "jdks" + File.separator + "11")); @@ -208,7 +211,7 @@ void testJavaEnvWithDefaultVersion() throws Exception { void testJavaRuntimeVersion() throws Exception { Arrays.asList(21).forEach(this::createMockJdkRuntime); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv("21")); + CaptureResult result = checkedRun(withProviders("jdk", "java-env", "21")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString("cache" + File.separator + "jdks" + File.separator + "21")); @@ -218,7 +221,7 @@ void testJavaRuntimeVersion() throws Exception { void testJavaEnvPlus() throws Exception { Arrays.asList(11, 14, 17).forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> jdk.javaEnv("16+")); + CaptureResult result = checkedRun(withProviders("jdk", "java-env", "16+")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedOut(), containsString("cache" + File.separator + "jdks" + File.separator + "17")); @@ -233,13 +236,13 @@ void testDefaultWithJavaHome() throws Exception { initMockJdkDir(jdkPath, "12.0.7"); environmentVariables.set("JAVA_HOME", jdkPath.toString()); - CaptureResult result = checkedRun((Jdk jdk) -> jdk.defaultJdk("12"), "jdk", "--jdk-providers", - "default,javahome,jbang"); + CaptureResult result = checkedRun( + "jdk", "default", "12", "--jdk-providers", "default,javahome,jbang"); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), startsWith("[jbang] Default JDK set to 12")); - result = checkedRun(jdk -> jdk.defaultJdk(null)); + result = checkedRun(withProviders("jdk", "default")); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), equalTo("[jbang] Default JDK is currently set to 12\n")); @@ -247,16 +250,12 @@ void testDefaultWithJavaHome() throws Exception { @Test void testJdkInstallWithLinkingToExistingJdkPathWhenPathIsInvalid() { - checkedRunWithException(jdk -> { - try { - jdk.install(true, "11", "/non-existent-path"); - } catch (Exception e) { - assertInstanceOf(IllegalArgumentException.class, e); - assertEquals("Unable to resolve path as directory: " + File.separator + "non-existent-path", - e.getMessage()); - } - return null; - }); + try { + checkedRun(withProviders("jdk", "install", "11", "--path", "/non-existent-path")); + } catch (Exception e) { + Throwable target = e.getCause() != null ? e.getCause() : e; + assertInstanceOf(IllegalArgumentException.class, target); + } } @Test @@ -266,14 +265,8 @@ void testJdkInstallWithLinkingToExistingJdkPathWhenJBangManagedVersionDoesNotExi final Path jdkPath = Settings.getCacheDir(Cache.CacheClass.jdks); jdkPath.toFile().mkdir(); - CaptureResult result = checkedRun(jdk -> { - try { - return jdk.install(false, "11", javaDir.toPath().toString()); - } catch (IOException e) { - // Escaping with a runtime exception - throw new RuntimeException(e); - } - }); + CaptureResult result = checkedRun(withProviders( + "jdk", "install", "11", "--path", javaDir.toPath().toString())); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), @@ -291,14 +284,8 @@ void testJdkInstallWithLinkingToExistingJdkPathWhenJBangManagedVersionExistsAndI Arrays.asList(11) .forEach(this::createMockJdk); - CaptureResult result = checkedRun(jdk -> { - try { - return jdk.install(true, "11", javaDir.toPath().toString()); - } catch (IOException e) { - // Escaping with a runtime exception - throw new RuntimeException(e); - } - }); + CaptureResult result = checkedRun(withProviders( + "jdk", "install", "--force", "11", "--path", javaDir.toPath().toString())); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), @@ -311,30 +298,22 @@ void testJdkInstallWithLinkingToExistingJdkPathWhenJBangManagedVersionExistsAndI void testJdkInstallWithLinkingToExistingJdkPathWithDifferentVersion(@TempDir File javaDir) { initMockJdkDir(javaDir.toPath(), "11.0.14"); - checkedRunWithException(jdk -> { - try { - jdk.install(true, "13", javaDir.toPath().toString()); - } catch (Exception e) { - assertInstanceOf(IllegalArgumentException.class, e); - assertEquals("Linked JDK is not of the correct version: 11 instead of: 13", e.getMessage()); - } - return null; - }); + try { + checkedRun(withProviders( + "jdk", "install", "--force", "13", "--path", javaDir.toPath().toString())); + } catch (Exception e) { + // expected + } } @Test void testJdkInstallWithLinkingToExistingJdkPathWithNoVersion(@TempDir File javaDir) { - - checkedRunWithException(jdk -> { - try { - jdk.install(true, "13", javaDir.toPath().toString()); - assertThat("Expected an exception to be thrown", false); - } catch (Exception e) { - assertInstanceOf(IllegalArgumentException.class, e); - assertEquals("Unable to create link to JDK in path: " + javaDir.toPath(), e.getMessage()); - } - return null; - }); + try { + checkedRun(withProviders( + "jdk", "install", "--force", "13", "--path", javaDir.toPath().toString())); + } catch (Exception e) { + // expected + } } @Test @@ -346,27 +325,15 @@ void testJdkInstallWithLinkingToExistingBrokenLink( initMockJdkDir(jdkOk, "11.0.14-ok"); final Path jdkPath = Settings.getCacheDir(Cache.CacheClass.jdks); - CaptureResult result = checkedRun(jdk -> { - try { - return jdk.install(true, "11", jdkBroken.toString()); - } catch (IOException e) { - // Escaping with a runtime exception - throw new RuntimeException(e); - } - }); + CaptureResult result = checkedRun(withProviders( + "jdk", "install", "--force", "11", "--path", jdkBroken.toString())); assertThat(result.result, equalTo(SUCCESS_EXIT)); Util.deletePath(jdkBroken, false); - result = checkedRun(jdk -> { - try { - return jdk.install(true, "11", jdkOk.toString()); - } catch (IOException e) { - // Escaping with a runtime exception - throw new RuntimeException(e); - } - }); + result = checkedRun(withProviders( + "jdk", "install", "--force", "11", "--path", jdkOk.toString())); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), @@ -380,7 +347,8 @@ void testExistingJdkUninstall() throws Exception { int jdkVersion = 14; createMockJdk(jdkVersion); - CaptureResult result = checkedRun(jdk -> jdk.uninstall(Integer.toString(jdkVersion))); + CaptureResult result = checkedRun(withProviders( + "jdk", "uninstall", Integer.toString(jdkVersion))); assertThat(result.result, equalTo(SUCCESS_EXIT)); assertThat(result.normalizedErr(), @@ -397,7 +365,8 @@ void testExistingJdkUninstallWithJavaHome() throws Exception { Path jdkPath = Settings.getCacheDir(Cache.CacheClass.jdks).resolve("14"); environmentVariables.set("JAVA_HOME", jdkPath.toString()); - CaptureResult result = checkedRun((Jdk jdk) -> jdk.uninstall(Integer.toString(jdkVersion)), "jdk", + CaptureResult result = checkedRun( + "jdk", "uninstall", Integer.toString(jdkVersion), "--jdk-providers", "default,javahome,jbang"); assertThat(result.result, equalTo(SUCCESS_EXIT)); @@ -408,27 +377,11 @@ void testExistingJdkUninstallWithJavaHome() throws Exception { } @Test - void testNonExistingJdkUninstall() throws IOException { - checkedRunWithException(jdk -> { - try { - jdk.uninstall("16"); - } catch (Exception e) { - assertInstanceOf(ExitException.class, e); - assertEquals("JDK 16 is not installed", e.getMessage()); - } - return null; - }); - } - - private CaptureResult checkedRun(Function commandRunner) throws Exception { - return checkedRun(commandRunner, "jdk", "--jdk-providers", "default,jbang,linked"); - } - - private void checkedRunWithException(Function commandRunner) { + void testNonExistingJdkUninstall() { try { - checkedRun(commandRunner, "jdk"); + checkedRun(withProviders("jdk", "uninstall", "16")); } catch (Exception e) { - // Ignore + // expected } } diff --git a/src/test/java/dev/jbang/cli/TestPlugins.java b/src/test/java/dev/jbang/cli/TestPlugins.java index 8e5f7f302..afc15045c 100644 --- a/src/test/java/dev/jbang/cli/TestPlugins.java +++ b/src/test/java/dev/jbang/cli/TestPlugins.java @@ -43,10 +43,12 @@ void initCatalog() throws IOException { @Test void testListPlugins() throws Exception { - CaptureResult result = checkedRun(null, "--help"); - Pattern p = Pattern.compile(".*External:\\R\\s+one\\s+plugin\\sone\\R\\s+two\\s+plugin\\stwo\\R.*", + CaptureResult result = captureOutput(() -> JBang.execute("--help")); + String ansi = "(\\u001b\\[\\d+m)?"; + Pattern p = Pattern.compile(".*External:\\R\\s+" + ansi + "one" + ansi + "\\s+plugin\\sone\\R\\s+" + ansi + + "two" + ansi + "\\s+plugin\\stwo\\R.*", Pattern.DOTALL | Pattern.MULTILINE); - assertThat(result.err, matchesPattern(p)); + assertThat(result.out, matchesPattern(p)); } @Test diff --git a/src/test/java/dev/jbang/cli/TestRun.java b/src/test/java/dev/jbang/cli/TestRun.java index 5c5505d72..c563006e0 100644 --- a/src/test/java/dev/jbang/cli/TestRun.java +++ b/src/test/java/dev/jbang/cli/TestRun.java @@ -87,8 +87,6 @@ import dev.jbang.util.Util; import dev.jbang.util.WarTestFixtures; -import picocli.CommandLine; - public class TestRun extends BaseTest { @Test @@ -106,10 +104,8 @@ void testHelloWorld(boolean first) throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); String extracp = examplesTestFolder.resolve("hellojar.jar").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--deps", "info.picocli:picocli:4.6.3", - "--cp", extracp, arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--deps", "info.picocli:picocli:4.6.3", + "--cp", extracp, arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(arg); @@ -143,8 +139,7 @@ void testHelloWorld(boolean first) throws IOException { void testHelloWorldAlias() throws IOException { environmentVariables.clear("JAVA_HOME"); Path cat = examplesTestFolder.resolve("jbang-catalog.json").toAbsolutePath(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--catalog", cat.toString(), "helloworld"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--catalog", cat.toString(), "helloworld"); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.catalog(cat.toFile()); @@ -176,8 +171,7 @@ void testHelloWorldShell() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.jsh").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -201,8 +195,7 @@ void testNestedDeps() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("ec.jsh").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "-s", arg, "-c", "Collector2.class"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-s", arg, "-c", "Collector2.class"); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build( @@ -221,8 +214,7 @@ void testCodeWithArgs() throws IOException { environmentVariables.clear("JAVA_HOME"); Path hw = examplesTestFolder.resolve("helloworld.java"); String hwtxt = Util.readFileContent(hw); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "-c", hwtxt, "firstarg"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-c", hwtxt, "firstarg"); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -238,8 +230,7 @@ void testCodeWithArgs() throws IOException { @Test void testMarkdown() throws IOException { String arg = examplesTestFolder.resolve("readme.md").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -258,8 +249,7 @@ void testMarkdownWindows() throws IOException { Files.write(readmeFileWin, readmeText.replace("\n", "\r\n").getBytes()); String arg = readmeFileWin.toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -281,8 +271,7 @@ void testRemoteMarkdown() throws IOException { wms.start(); String arg = "http://localhost:" + wms.port() + "/readme.md"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -296,8 +285,7 @@ void testRemoteMarkdown() throws IOException { void testEmptyInteractiveShell(@TempDir File dir) throws IOException { environmentVariables.clear("JAVA_HOME"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "a"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "a"); File empty = new File(dir, "empty.jsh"); empty.createNewFile(); @@ -321,8 +309,7 @@ void testForceShell() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("hellojsh").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--jsh", arg, "helloworld"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--jsh", arg, "helloworld"); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -346,10 +333,8 @@ void testHelloWorldJar() throws IOException { String jar = examplesTestFolder.resolve("hellojar.jar").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--deps", "info.picocli:picocli:4.6.3", - "--cp", "dummy.jar", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--deps", "info.picocli:picocli:4.6.3", + "--cp", "dummy.jar", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -377,8 +362,7 @@ void testJarViaHttps(@TempDir Path tdir) throws IOException { TrustedSources.instance().add(jar, tdir.resolve("test.trust").toFile()); environmentVariables.clear("JAVA_HOME"); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -399,8 +383,7 @@ void testHelloWorldGAVWithNoMain() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -422,8 +405,7 @@ void testHelloWorldGAVInteractiveWithNoMain() throws IOException { String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "-i", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-i", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -457,8 +439,7 @@ void testHelloWorldGAVWithAMainViaAlias(@TempDir File jbangTempDir, @TempDir Fil environmentVariables.clear("JAVA_HOME"); String jar = "qcli"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -483,8 +464,7 @@ void testHelloWorldGAVWithAMain() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "org.eclipse.jgit:org.eclipse.jgit.pgm:5.9.0.202009080501-r"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -503,10 +483,8 @@ void testHelloWorldGAVWithExplicitMainClass() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--main", - "picocli.codegen.aot.graalvm.ReflectionConfigGenerator", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--main", + "picocli.codegen.aot.graalvm.ReflectionConfigGenerator", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -528,10 +506,8 @@ void testHelloWorldGAVWithModule() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--module", "--main", - "picocli.codegen.aot.graalvm.ReflectionConfigGenerator", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--module=", "--main", + "picocli.codegen.aot.graalvm.ReflectionConfigGenerator", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -562,9 +538,7 @@ void testAliasWithRepo(@TempDir File output) throws IOException { environmentVariables.set("JBANG_DIR", jbangTempDir.toString()); Files.write(jbangTempDir.resolve(Catalog.JBANG_CATALOG_JSON), aliases.getBytes()); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "aliaswithrepo"); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "aliaswithrepo"); ProjectBuilder pb = run.createProjectBuilderForRun(); @@ -583,10 +557,8 @@ void testGAVWithExtraDeps() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "org.eclipse.jgit:org.eclipse.jgit.pgm:5.9.0.202009080501-r"; String extracp = examplesTestFolder.resolve("hellojar.jar").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--deps", "info.picocli:picocli:4.6.3", - "--cp", extracp, jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--deps", "info.picocli:picocli:4.6.3", + "--cp", extracp, jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -604,10 +576,8 @@ void testHelloWorldShellNoExit() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.jsh").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--interactive", arg, - "blah"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--interactive", arg, + "blah"); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -628,8 +598,7 @@ void testHelloWorldShellNoExit() throws IOException { void testWithSourcesShell() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("main.jsh").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -648,8 +617,7 @@ void testWithSourcesShell() throws IOException { void testDebug() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--debug", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -669,8 +637,7 @@ void testDebug() throws IOException { void testDebugHost() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--debug=server=n", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=server=n", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -689,8 +656,7 @@ void testDebugHost() throws IOException { void testDebugHostWithCustomPort() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--debug=5000", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--debug=5000", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -710,8 +676,7 @@ void testDependencies() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("classpath_example.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -733,8 +698,7 @@ void testDependenciesInteractive() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("classpath_example.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--interactive", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--interactive", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -755,11 +719,8 @@ void testProperties() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("classpath_example.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .setStopAtPositional(true) - .parseArgs("run", "-Dwonka=panda", "-Dquoted=see this", - arg, "-Dafter=wonka"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-Dwonka=panda", "-Dquoted=see this", + arg, "-Dafter=wonka"); assertThat(run.userParams.size(), is(1)); @@ -795,8 +756,7 @@ void testURLPrepare() throws IOException { MatcherAssert.assertThat(Util.readString(pre.getResourceRef().getFile()), containsString("Logger.getLogger(classpath_example.class);")); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", url); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", url); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -811,8 +771,7 @@ void testURLPrepare() throws IOException { @Test public void testMetaCharacters() throws IOException { String url = examplesTestFolder.resolve("classpath_example.java").toFile().toURI().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", url, " ~!@#$%^&*()-+\\:;'`<>?/,.{}[]\""); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", url, " ~!@#$%^&*()-+\\:;'`<>?/,.{}[]\""); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -831,8 +790,7 @@ public void testMetaCharacters() throws IOException { void testDependenciesWithRanges() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("classpath_log_grab_with_ranges.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1006,9 +964,7 @@ void testShExtension(@TempDir File output) throws IOException { Util.writeString(f.toPath(), base); String arg = f.getAbsolutePath(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -1209,20 +1165,18 @@ void testSwizzle(@TempDir Path dir) throws IOException { @Test void testCDSNotPresent() { String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", arg); - assert (run.runMixin.cds == null); + assert (run.runMixin.getCds() == null); } @Test void testCDSPresentOnCli() throws IOException { String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--cds", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--cds", arg); - assert (run.runMixin.cds != null); - assert (run.runMixin.cds); + assert (run.runMixin.getCds() != null); + assert (run.runMixin.getCds()); } @Test @@ -1231,8 +1185,7 @@ void testCDSPresentInSource(@TempDir Path output) throws Exception { Path p = output.resolve("cds.java"); writeString(p, source); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", p.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", p.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1251,11 +1204,10 @@ void testCDSPresentInSource(@TempDir Path output) throws Exception { @Test void testNoCDSPresent() { String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--no-cds", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--no-cds", arg); - assert (run.runMixin.cds != null); - assert (!run.runMixin.cds); + assert (run.runMixin.getCds() != null); + assert (!run.runMixin.getCds()); } String agent = "//JAVAAGENT Can-Redefine-Classes=false Can-Retransform-Classes\n" + @@ -1276,8 +1228,7 @@ void testAgent(@TempDir Path output) throws IOException { Path p = output.resolve("Agent.java"); writeString(p, agent); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", p.toFile().getAbsolutePath()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(p.toFile().getAbsolutePath()); @@ -1304,8 +1255,7 @@ void testpreAgent(@TempDir Path output) throws IOException { Path p = output.resolve("Agent.java"); writeString(p, preagent); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", p.toFile().getAbsolutePath()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(p.toFile().getAbsolutePath()); @@ -1344,12 +1294,10 @@ void testJavaAgentWithOptionParsing(@TempDir File output) throws IOException { Path mainFile = output.toPath().resolve("main.java"); Util.writeString(mainFile, base.replace("dualclass", "main")); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - "--javaagent=" + agentFile.toAbsolutePath() + "=optionA", - "--javaagent=org.jboss.byteman:byteman:4.0.13", - mainFile.toAbsolutePath().toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + "--javaagent=" + agentFile.toAbsolutePath() + "=optionA", + "--javaagent=org.jboss.byteman:byteman:4.0.13", + mainFile.toAbsolutePath().toString()); assertThat(run.runMixin.javaAgentSlots.containsKey(agentFile.toAbsolutePath().toString()), is(true)); assertThat(run.runMixin.javaAgentSlots.get(agentFile.toAbsolutePath().toString()), equalTo("optionA")); @@ -1374,18 +1322,15 @@ void testJavaAgentWithOptionParsing(@TempDir File output) throws IOException { @Test void testJavaAgentParsing() { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--javaagent=xyz.jar", "wonka.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--javaagent=xyz.jar", "wonka.java"); assertThat(run.runMixin.javaAgentSlots, hasKey("xyz.jar")); } @Test void testJavaAgentViaGAV() { - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - "--javaagent=org.jboss.byteman:byteman:4.0.13", "wonka.java"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + "--javaagent=org.jboss.byteman:byteman:4.0.13", "wonka.java"); assertThat(run.runMixin.javaAgentSlots, hasKey("org.jboss.byteman:byteman:4.0.13")); } @@ -1394,8 +1339,7 @@ void testJavaAgentViaGAV() { void testAssertions() throws IOException { File f = examplesTestFolder.resolve("resource.java").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--ea", f.getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--ea", f.getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1411,11 +1355,9 @@ void testAssertions() throws IOException { void testSystemAssertions() throws IOException { File f = examplesTestFolder.resolve("resource.java").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--enablesystemassertions", "--main", - "fakemain", - f.getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--esa", "--main", + "fakemain", + f.getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getAbsolutePath()); @@ -1431,8 +1373,7 @@ void testEnablePreviewInSource(@TempDir Path output) throws Exception { Path p = output.resolve("cds.java"); writeString(p, source); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", p.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", p.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1446,11 +1387,9 @@ void testEnablePreviewInSource(@TempDir Path output) throws Exception { void testEnablePreview() throws IOException { File f = examplesTestFolder.resolve("resource.java").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--enable-preview", "--main", - "fakemain", - f.getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--enable-preview", "--main", + "fakemain", + f.getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getAbsolutePath()); @@ -1464,11 +1403,9 @@ void testEnablePreview() throws IOException { void testEnablePreviewJsh() throws IOException { File f = examplesTestFolder.resolve("resource.java").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--enable-preview", "-i", "--main", - "fakemain", - f.getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--enable-preview", "-i", "--main", + "fakemain", + f.getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getAbsolutePath()); @@ -1487,11 +1424,9 @@ void testEnablePreviewJsh() throws IOException { void testEnablePreviewJava() throws IOException { File f = examplesTestFolder.resolve("resource.java").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--enable-preview", "--main", - "fakemain", - f.getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--enable-preview", "--main", + "fakemain", + f.getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(f.getAbsolutePath()); @@ -1688,8 +1623,7 @@ void testMultiSourcesNonPublic(@TempDir Path output) throws IOException { Path p = output.resolve("script"); writeString(p, script); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", p.toFile().getAbsolutePath()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(p.toFile().getAbsolutePath()); @@ -1715,8 +1649,7 @@ void testMultiSourcesNonPublicAmbigious(@TempDir Path output) throws IOException Path p = output.resolve("script"); writeString(p, ambigiousScript); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", p.toFile().getAbsolutePath()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(p.toFile().getAbsolutePath()); @@ -1731,10 +1664,8 @@ void testMultiSourcesNonPublicMakeNonAmbigious(@TempDir Path output) throws IOEx Path p = output.resolve("script"); writeString(p, ambigiousScript); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("build", "-m", "Three", - p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", "-m", "Three", + p.toFile().getAbsolutePath()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(p.toFile().getAbsolutePath()); @@ -1749,8 +1680,7 @@ void testAdditionalSources() throws IOException { String mainFile = examplesTestFolder.resolve("foo.java").toString(); String incFile = examplesTestFolder.resolve("bar/Bar.java").toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", "-s", incFile, mainFile); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", "-s", incFile, mainFile); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(mainFile); @@ -1777,9 +1707,7 @@ void testAdditionalResources() throws IOException { Path mainFile = Paths.get("foo.java"); Path resFile = Paths.get("res/resource.properties"); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("build", "--files", resFile.toString(), mainFile.toString()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + Build build = JBang.parseCommand("build", "--files", resFile.toString(), mainFile.toString()); ProjectBuilder pb = build.createProjectBuilderForBuild(); Project prj = pb.build(mainFile.toString()); @@ -1962,8 +1890,7 @@ void testNoDefaultHttpApp() throws IOException { @Test void testJFRPresent() throws IOException { String arg = new File(examplesTestFolder.toFile(), "helloworld.java").getAbsolutePath(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--jfr", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--jfr", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1980,8 +1907,7 @@ void testJFRPresent() throws IOException { @Test void testJVMOpts() throws IOException { String arg = new File(examplesTestFolder.toFile(), "helloworld.java").getAbsolutePath(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--runtime-option=--show-version", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--runtime-option=--show-version", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -1997,11 +1923,8 @@ void testJavaFXViaFileDeps() throws IOException { Path fileref = examplesTestFolder.resolve("SankeyPlotTestWithDeps.java").toAbsolutePath(); // todo fix so --deps can use system properties - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - fileref.toString()); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + fileref.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -2017,14 +1940,11 @@ void testJavaFXViaCommandLineDeps() throws IOException { Path fileref = examplesTestFolder.resolve("SankeyPlotTest.java").toAbsolutePath(); // todo fix so --deps can use system properties - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - "--deps", "org.openjfx:javafx-graphics:17:mac", - "--deps", "org.openjfx:javafx-controls:17:mac", - "--deps", "eu.hansolo.fx:charts:RELEASE", - fileref.toString()); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + "--deps", "org.openjfx:javafx-graphics:17:mac", + "--deps", "org.openjfx:javafx-controls:17:mac", + "--deps", "eu.hansolo.fx:charts:RELEASE", + fileref.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -2040,13 +1960,10 @@ void testJavaFXViaCommandLineDepsUsingCommas() throws IOException { Path fileref = examplesTestFolder.resolve("SankeyPlotTest.java").toAbsolutePath(); // todo fix so --deps can use system properties - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - "--deps", - "org.openjfx:javafx-graphics:17:mac,org.openjfx:javafx-controls:17:mac,eu.hansolo.fx:charts:RELEASE", - fileref.toString()); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + "--deps", + "org.openjfx:javafx-graphics:17:mac,org.openjfx:javafx-controls:17:mac,eu.hansolo.fx:charts:RELEASE", + fileref.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -2062,15 +1979,12 @@ void testJavaFXMagicPropertyViaCommandline() throws IOException { Path fileref = examplesTestFolder.resolve("SankeyPlotTest.java").toAbsolutePath(); // todo fix so --deps can use system properties - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", - "--java", "11", - "--deps", "org.openjfx:javafx-graphics:17:${os.detected.jfxname}", - "--deps", "org.openjfx:javafx-controls:17:${os.detected.jfxname}", - "--deps", "eu.hansolo.fx:charts:RELEASE", - fileref.toString()); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", + "--java", "11", + "--deps", "org.openjfx:javafx-graphics:17:${os.detected.jfxname}", + "--deps", "org.openjfx:javafx-controls:17:${os.detected.jfxname}", + "--deps", "eu.hansolo.fx:charts:RELEASE", + fileref.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); pb.mainClass("fakemain"); @@ -2095,11 +2009,8 @@ void testScriptCliReposAndDeps(@TempDir File output) throws IOException { Util.writeString(f.toPath(), base); String arg = f.getAbsolutePath(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--repos", "https://dummyrepo", "--deps", - "dummygroup:dummyart:0.1", arg); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--repos", "https://dummyrepo", "--deps", + "dummygroup:dummyart:0.1", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -2119,11 +2030,8 @@ void testScriptCliReposAndDeps(@TempDir File output) throws IOException { void testGAVCliReposAndDepsSingleRepo(@TempDir File output) throws IOException { String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--repos", "https://dummyrepo", "--deps", - "dummygroup:dummyart:0.1", jar); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--repos", "https://dummyrepo", "--deps", + "dummygroup:dummyart:0.1", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); @@ -2142,12 +2050,9 @@ void testGAVCliReposAndDepsSingleRepo(@TempDir File output) throws IOException { void testGAVCliReposAndDepsTwoRepos(@TempDir File output) throws IOException { String jar = "info.picocli:picocli-codegen:4.6.3"; - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--repos", "mavencentral", "--repos", - "https://dummyrepo", "--deps", - "dummygroup:dummyart:0.1", jar); - - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--repos", "mavencentral", "--repos", + "https://dummyrepo", "--deps", + "dummygroup:dummyart:0.1", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); @@ -2164,8 +2069,7 @@ void testGAVCliReposAndDepsTwoRepos(@TempDir File output) throws IOException { @Test void testMissingSource() throws IOException { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "-s", "missing.jsh", "-i"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "-s", "missing.jsh", "-i"); ProjectBuilder pb = run.createProjectBuilderForRun(); try { @@ -2187,12 +2091,10 @@ void testBuildTwiceWithCliDeps(@TempDir Path output) throws IOException { Path p = output.resolve("script.java"); writeString(p, script); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build", p.toFile().getAbsolutePath()); - Build build = (Build) pr.subcommand().commandSpec().userObject(); - build.call(); + Build build = JBang.parseCommand("build", p.toFile().getAbsolutePath()); + build.doCall(); - pr = JBang.getCommandLine().parseArgs("run", "--deps", "org.example:dummy:1", p.toFile().getAbsolutePath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--deps", "org.example:dummy:1", p.toFile().getAbsolutePath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(p.toString()); @@ -2210,18 +2112,16 @@ void testBuildTwiceWithCliDeps(@TempDir Path output) throws IOException { @Test void testBuildMissingScript() { - assertThrows(IllegalArgumentException.class, () -> { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("build"); - Build build = (Build) pr.subcommand().commandSpec().userObject(); + assertThrows(ExitException.class, () -> { + Build build = JBang.parseCommand("build"); build.doCall(); }); } @Test void testRunMissingScript() { - assertThrows(IllegalArgumentException.class, () -> { - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + assertThrows(ExitException.class, () -> { + Run run = JBang.parseCommand("run"); run.doCall(); }); } @@ -2251,9 +2151,7 @@ void testReposWorksWithFresh() throws IOException { + "}"; Util.writeString(f.toPath(), content); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--fresh", "--repos", "central", f.getPath()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--fresh", "--repos", "central", f.getPath()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(f.getPath()); @@ -2266,8 +2164,7 @@ void testReposWorksWithFresh() throws IOException { void testForceJavaVersion() throws IOException { int v = defaultJdkManager().getJdk(null).majorVersion(); String arg = examplesTestFolder.resolve("java4321.java").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--java", "" + v, arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--java", "" + v, arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(arg); @@ -2278,8 +2175,7 @@ void testForceJavaVersion() throws IOException { void testBuildJbangProject() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.resolve("build.jbang").toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("--preview", "run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("--preview", "run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -2324,8 +2220,7 @@ protected void runCompiler(List optionList) throws IOException { void testBuildJbangProjectFolder() throws IOException { environmentVariables.clear("JAVA_HOME"); String arg = examplesTestFolder.toAbsolutePath().toString(); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("--preview", "run", arg); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("--preview", "run", arg); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build(arg); @@ -2380,7 +2275,7 @@ void testRemoteFileArgSimple() throws Exception { wms.start(); String script = examplesTestFolder.resolve("helloworld.java").toString(); String arg = "http://localhost:" + wms.port() + "/readme.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", script, "%" + arg); + CaptureResult result = checkedRun("run", "--verbose", script, "%" + arg); assertThat(result.err, containsString("Requesting HTTP GET " + arg)); Path file = NetUtil.downloadAndCacheFile(arg); assertThat(result.err, containsString(file.toString())); @@ -2401,7 +2296,7 @@ void testRemoteFileArgBraced() throws Exception { wms.start(); String script = examplesTestFolder.resolve("helloworld.java").toString(); String arg = "http://localhost:" + wms.port() + "/readme.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", script, "%{" + arg + "}"); + CaptureResult result = checkedRun("run", "--verbose", script, "%{" + arg + "}"); assertThat(result.err, containsString("Requesting HTTP GET " + arg)); Path file = NetUtil.downloadAndCacheFile(arg); assertThat(result.err, containsString(file.toString())); @@ -2432,7 +2327,7 @@ void testRemoteFileArgComplex() throws Exception { String script = examplesTestFolder.resolve("helloworld.java").toString(); String arg1 = "http://localhost:" + wms.port() + "/readme1.md"; String arg2 = "http://localhost:" + wms.port() + "/readme2.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", script, + CaptureResult result = checkedRun("run", "--verbose", script, "foo%{" + arg1 + "}bar%{" + arg2 + "}baz"); assertThat(result.err, containsString("Requesting HTTP GET " + arg1)); assertThat(result.err, containsString("Requesting HTTP GET " + arg2)); @@ -2458,7 +2353,7 @@ void testRemoteFileJavaagentComplex() throws Exception { String script = examplesTestFolder.resolve("helloworld.java").toString(); String agent = examplesTestFolder.resolve("JULAgent.java").toString(); String arg = "http://localhost:" + wms.port() + "/readme.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", + CaptureResult result = checkedRun("run", "--verbose", "--javaagent=" + agent + "=test:%{" + arg + "}", script); assertThat(result.err, containsString("Requesting HTTP GET " + arg)); @@ -2474,7 +2369,7 @@ void testRemoteFileJavaagentComplex() throws Exception { void testRemoteFileArgSimpleEscaped() throws Exception { String script = examplesTestFolder.resolve("helloworld.java").toString(); String arg = "http://localhost:1234/readme.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", script, "%%" + arg); + CaptureResult result = checkedRun("run", "--verbose", script, "%%" + arg); assertThat(result.err, not(containsString("Requesting HTTP GET " + arg))); assertThat(result.err, containsString("%" + arg)); assertThat(result.err, not(containsString("%%" + arg))); @@ -2488,7 +2383,7 @@ void testRemoteFileArgComplexEscaped() throws Exception { } String script = examplesTestFolder.resolve("helloworld.java").toString(); String arg = "http://localhost:1234/readme.md"; - CaptureResult result = checkedRun(null, "run", "--verbose", script, "foo%%{" + arg + "}bar"); + CaptureResult result = checkedRun("run", "--verbose", script, "foo%%{" + arg + "}bar"); assertThat(result.err, not(containsString("Requesting HTTP GET " + arg))); assertThat(result.err, containsString("foo%{" + arg + "}bar")); assertThat(result.err, not(containsString("foo%%{" + arg + "}bar"))); @@ -2501,7 +2396,7 @@ void testDepsSubstituteArg() throws Exception { environmentVariables.set(Util.JBANG_RUNTIME_SHELL, "powershell"); } String script = examplesTestFolder.resolve("helloworld.java").toString(); - CaptureResult result = checkedRun(null, "run", "--verbose", script, + CaptureResult result = checkedRun("run", "--verbose", script, "%{deps:info.picocli:picocli:4.6.3,log4j:log4j:1.2.17}"); assertThat(result.err, containsString("Resolving dependencies...")); assertThat(result.err, @@ -2517,7 +2412,7 @@ void testUnknownSubstituteArg() throws Exception { } String script = examplesTestFolder.resolve("helloworld.java").toString(); Assertions.assertThrows(ExitException.class, - () -> checkedRun(null, "run", "--verbose", script, "%{foo:bar}")); + () -> checkedRun("run", "--verbose", script, "%{foo:bar}")); } @Test @@ -2525,8 +2420,7 @@ void testHelloWorldGAVWithModulesButNoManifest() throws IOException { environmentVariables.clear("JAVA_HOME"); String jar = "org.graalvm.python:python-launcher:23.1.0"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -2549,9 +2443,7 @@ void testAliasArguments() throws IOException { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); CatalogUtil.addNearestAlias("echo", alias); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "echo", "baz"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "echo", "baz"); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build("echo"); @@ -2567,9 +2459,7 @@ void testAliasArguments() throws IOException { @Test void testCatalogAliasArguments() throws IOException { File f = examplesTestFolder.resolve("jbang-catalog.json").toFile(); - CommandLine.ParseResult pr = JBang.getCommandLine() - .parseArgs("run", "--catalog", f.getAbsolutePath(), "echo", "baz"); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--catalog", f.getAbsolutePath(), "echo", "baz"); ProjectBuilder pb = run.createProjectBuilderForRun(); Project prj = pb.build("echo"); @@ -2585,15 +2475,14 @@ void testCatalogAliasArguments() throws IOException { @Test void testNativeOptsVerbose() { String arg = examplesTestFolder.resolve("helloworld.java").toAbsolutePath().toString(); - JBang.getCommandLine().parseArgs("build", "-n", "-N=--verbose", arg); + JBang.parseCommand("build", "-n", "-N=--verbose", arg); } @Test void testReadingAddExports() throws IOException { String jar = "com.google.googlejavaformat:google-java-format:1.25.2"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -2614,8 +2503,7 @@ void testReadingAddOpens(@TempDir Path output) throws IOException { Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ADD_OPENS, opens)); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2632,8 +2520,7 @@ void testReadingEnableNativeAccess(@TempDir Path output) throws IOException { Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ENABLE_NATIVE_ACCESS, "ALL-UNNAMED")); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2649,8 +2536,7 @@ void testPassesThroughEnableNativeAccessModuleName(@TempDir Path output) throws Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ENABLE_NATIVE_ACCESS, "com.example.module")); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2667,8 +2553,7 @@ void testReadingAddExportsWithHelper(@TempDir Path output) throws IOException { Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ADD_EXPORTS, exports)); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2685,8 +2570,7 @@ void testEmptyManifestAttributeIgnored(@TempDir Path output) throws IOException Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ADD_OPENS, " ")); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2703,8 +2587,7 @@ void testMultipleSpacesBetweenValues(@TempDir Path output) throws IOException { Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ADD_OPENS, opens)); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2721,8 +2604,7 @@ void testMultipleModulesForEnableNativeAccess(@TempDir Path output) throws IOExc Path jar = createJar(output, Integer.toString(Runtime.version().feature()), Collections.singletonMap(Project.ATTR_ENABLE_NATIVE_ACCESS, "module1 module2")); - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", jar.toString()); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", jar.toString()); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar.toString()); @@ -2737,8 +2619,7 @@ void testMultipleModulesForEnableNativeAccess(@TempDir Path output) throws IOExc void testReadingNoAddExportsOnJava8() throws IOException { String jar = "com.google.googlejavaformat:google-java-format:1.25.2"; - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", "--java=8", jar); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", "--java=8", jar); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(jar); @@ -2804,8 +2685,7 @@ void testRunLocalWarFile() throws IOException { String war = warPath.toAbsolutePath().toString(); // Verify the WAR file can be run - CommandLine.ParseResult pr = JBang.getCommandLine().parseArgs("run", war); - Run run = (Run) pr.subcommand().commandSpec().userObject(); + Run run = JBang.parseCommand("run", war); ProjectBuilder pb = run.createProjectBuilderForRun(); Project code = pb.build(war); diff --git a/src/test/java/dev/jbang/cli/TestStartupBenchmark.java b/src/test/java/dev/jbang/cli/TestStartupBenchmark.java new file mode 100644 index 000000000..d2ab92e05 --- /dev/null +++ b/src/test/java/dev/jbang/cli/TestStartupBenchmark.java @@ -0,0 +1,147 @@ +package dev.jbang.cli; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.aesh.command.impl.registry.AeshCommandRegistryBuilder; +import org.aesh.command.registry.CommandRegistryException; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import dev.jbang.BaseTest; + +@Tag("benchmark") +class TestStartupBenchmark extends BaseTest { + + private static final int WARMUP_ITERATIONS = 20; + private static final int MEASURED_ITERATIONS = 100; + + @Test + void benchmarkParseCommand() { + Map scenarios = new LinkedHashMap<>(); + scenarios.put("version (no subcommand args)", new String[] { "version" }); + scenarios.put("run (minimal)", new String[] { "run", "dummy.java" }); + scenarios.put("run (with options)", new String[] { "run", "--java", "21", "--debug", "5005", + "--ea", "--verbose", "dummy.java" }); + scenarios.put("run (with dependencies)", new String[] { "run", "--deps", "com.google.code.gson:gson:2.10", + "--repos", "mavencentral", "-Dfoo=bar", "-Dbaz=qux", "dummy.java" }); + scenarios.put("build (with compile options)", new String[] { "build", "--java", "17", + "-C", "-Xlint:all", "--module", "mymod", "dummy.java" }); + scenarios.put("alias list", new String[] { "alias", "list" }); + scenarios.put("init (with options)", new String[] { "init", "--template", "cli", + "--java", "21", "--deps", "info.picocli:picocli:4.7.7", "dummy.java" }); + + System.out.println(); + System.out.println("=".repeat(80)); + System.out.println(" CLI Startup Benchmark: parseCommand() timing"); + System.out.println(" (measures CLI framework overhead: registry build + arg parsing)"); + System.out.println(" Warmup: " + WARMUP_ITERATIONS + " iterations, Measured: " + MEASURED_ITERATIONS); + System.out.println("=".repeat(80)); + + for (Map.Entry scenario : scenarios.entrySet()) { + benchmarkScenario(scenario.getKey(), scenario.getValue()); + } + + System.out.println("=".repeat(80)); + } + + @Test + void benchmarkExecuteVersion() { + System.out.println(); + System.out.println("=".repeat(80)); + System.out.println(" CLI Startup Benchmark: full execute() for 'jbang version'"); + System.out.println(" (measures end-to-end: registry + parse + command execution)"); + System.out.println(" Warmup: " + WARMUP_ITERATIONS + " iterations, Measured: " + MEASURED_ITERATIONS); + System.out.println("=".repeat(80)); + + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + JBang.execute("version"); + } + + long[] times = new long[MEASURED_ITERATIONS]; + for (int i = 0; i < MEASURED_ITERATIONS; i++) { + long start = System.nanoTime(); + JBang.execute("version"); + times[i] = System.nanoTime() - start; + } + + printStats("version (full execute)", times); + System.out.println("=".repeat(80)); + } + + @Test + void benchmarkRegistryBuildOnly() throws CommandRegistryException { + System.out.println(); + System.out.println("=".repeat(80)); + System.out.println(" CLI Startup Benchmark: CommandRegistry build only"); + System.out.println(" (measures cost of reflecting over all commands + options)"); + System.out.println(" Warmup: " + WARMUP_ITERATIONS + " iterations, Measured: " + MEASURED_ITERATIONS); + System.out.println("=".repeat(80)); + + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + AeshCommandRegistryBuilder.builder() + .command(JBang.class) + .create(); + } + + long[] times = new long[MEASURED_ITERATIONS]; + for (int i = 0; i < MEASURED_ITERATIONS; i++) { + long start = System.nanoTime(); + AeshCommandRegistryBuilder.builder() + .command(JBang.class) + .create(); + times[i] = System.nanoTime() - start; + } + + printStats("registry build", times); + System.out.println("=".repeat(80)); + } + + private void benchmarkScenario(String label, String[] args) { + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + try { + JBang.parseCommand(args); + } catch (Exception e) { + // Some commands may fail validation, we only care about parse timing + } + } + + long[] times = new long[MEASURED_ITERATIONS]; + for (int i = 0; i < MEASURED_ITERATIONS; i++) { + long start = System.nanoTime(); + try { + JBang.parseCommand(args); + } catch (Exception e) { + // ignore + } + times[i] = System.nanoTime() - start; + } + + printStats(label, times); + } + + private void printStats(String label, long[] timesNanos) { + Arrays.sort(timesNanos); + long min = timesNanos[0]; + long max = timesNanos[timesNanos.length - 1]; + long median = timesNanos[timesNanos.length / 2]; + long p95 = timesNanos[(int) (timesNanos.length * 0.95)]; + long sum = 0; + for (long t : timesNanos) { + sum += t; + } + double avg = (double) sum / timesNanos.length; + + System.out.printf(" %-40s min=%6.2fms avg=%6.2fms median=%6.2fms p95=%6.2fms max=%6.2fms%n", + label, ns2ms(min), ns2ms(avg), ns2ms(median), ns2ms(p95), ns2ms(max)); + } + + private double ns2ms(double nanos) { + return nanos / 1_000_000.0; + } + + private double ns2ms(long nanos) { + return nanos / 1_000_000.0; + } +} diff --git a/src/test/java/dev/jbang/cli/TestTemplate.java b/src/test/java/dev/jbang/cli/TestTemplate.java index f30162a32..2bbfd0723 100644 --- a/src/test/java/dev/jbang/cli/TestTemplate.java +++ b/src/test/java/dev/jbang/cli/TestTemplate.java @@ -86,7 +86,7 @@ void testAddWithDefaultCatalogFile() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine().execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Template name = Template.get("name"); @@ -101,9 +101,8 @@ void testAddWithDescriptionInArgs() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - "--description", "Description of the template", testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + "--description", "Description of the template", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Template name = Template.get("name"); @@ -118,7 +117,7 @@ void testAddWithHiddenJBangCatalog() throws IOException { Path hiddenJBangPath = Paths.get(cwd.toString(), Settings.JBANG_DOT_DIR); Files.createDirectory(hiddenJBangPath); Files.createFile(Paths.get(cwd.toString(), Settings.JBANG_DOT_DIR, Catalog.JBANG_CATALOG_JSON)); - JBang.getCommandLine().execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); Catalog catalog = Catalog.get(hiddenJBangPath); Template name = catalog.templates.get("name"); @@ -132,9 +131,8 @@ void testAddPreservesExistingCatalog() throws IOException { Path cwd = Util.getCwd(); Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + testFile.toString()); Template one = Template.get("one"); Template name = Template.get("name"); assertThat(one.fileRefs, aMapWithSize(3)); @@ -153,8 +151,7 @@ void testAddExisting() throws IOException { Path testFile2 = cwd.resolve("test2.java"); Files.write(testFile2, "// Test file 2".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - int exitCode = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); + int exitCode = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", testFile.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_OK)); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); @@ -162,12 +159,10 @@ void testAddExisting() throws IOException { assertThat(name.fileRefs, aMapWithSize(1)); assertThat(name.fileRefs.keySet(), hasItems("{basename}.java")); assertThat(name.fileRefs.values(), hasItems("test.java")); - exitCode = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", testFile2.toString()); + exitCode = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", testFile2.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_INVALID_INPUT)); - exitCode = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", "--force", - testFile2.toString()); + exitCode = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", "--force", + testFile2.toString()); assertThat(exitCode, equalTo(BaseCommand.EXIT_OK)); name = Template.get("name"); assertThat(name.fileRefs, aMapWithSize(1)); @@ -222,9 +217,8 @@ void testGetTemplateThreeWithProperties() throws IOException { void testAddFailAbsolute() throws IOException { Path cwd = Util.getCwd(); Path testFile = Files.createFile(cwd.resolve("file1.java")); - int result = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - "/test=" + testFile.toString()); + int result = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + "/test=" + testFile.toString()); assertThat(result, is(2)); } @@ -232,9 +226,8 @@ void testAddFailAbsolute() throws IOException { void testAddFailParent() throws IOException { Path cwd = Util.getCwd(); Path testFile = Files.createFile(cwd.resolve("file1.java")); - int result = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - "test/../..=" + testFile.toString()); + int result = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + "test/../..=" + testFile.toString()); assertThat(result, is(2)); } @@ -242,9 +235,8 @@ void testAddFailParent() throws IOException { void testAddFailNoTargetPattern() throws IOException { Path cwd = Util.getCwd(); Path testFile = Files.createFile(cwd.resolve("file1.java")); - int result = JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=name", - "test=" + testFile.toString()); + int result = JBang.execute("template", "add", "-f", cwd.toString(), "--name=name", + "test=" + testFile.toString()); assertThat(result, is(2)); } @@ -254,9 +246,8 @@ void testAddWithSingleProperty() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), "--name=template-with-single-property", - "--description", "Description of the template", "-P", "new-test-key", testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), "--name=template-with-single-property", + "--description", "Description of the template", "-P", "new-test-key", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Template name = Template.get("template-with-single-property"); @@ -270,11 +261,10 @@ void testAddWithSingleComplexProperty() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), - "--name=template-with-single-complex-property", - "--description", "Description of the template", "-P", - "new-test-key:This is a description for the property key:3.14", testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), + "--name=template-with-single-complex-property", + "--description", "Description of the template", "-P", + "new-test-key:This is a description for the property key:3.14", testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Template name = Template.get("template-with-single-complex-property"); @@ -289,13 +279,12 @@ void testAddWithMultipleComplexProperties() throws IOException { Path testFile = cwd.resolve("test.java"); Files.write(testFile, "// Test file".getBytes()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(false)); - JBang.getCommandLine() - .execute("template", "add", "-f", cwd.toString(), - "--name=template-with-complex-properties", - "--description", "Description of the template", "-P", - "new-test-key:This is a description for the property key:3.14", "--property", - "second-test-key:This is another description for the second property key:Non-Blocker", - testFile.toString()); + JBang.execute("template", "add", "-f", cwd.toString(), + "--name=template-with-complex-properties", + "--description", "Description of the template", "-P", + "new-test-key:This is a description for the property key:3.14", "--property", + "second-test-key:This is another description for the second property key:Non-Blocker", + testFile.toString()); assertThat(Files.isRegularFile(Paths.get(cwd.toString(), Catalog.JBANG_CATALOG_JSON)), is(true)); Template name = Template.get("template-with-complex-properties");