diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java b/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java deleted file mode 100644 index 5420dea..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/SopCLI.java +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package sop.cli.picocli; - -import picocli.AutoComplete; -import picocli.CommandLine; -import sop.SOP; -import sop.cli.picocli.commands.ArmorCmd; -import sop.cli.picocli.commands.ChangeKeyPasswordCmd; -import sop.cli.picocli.commands.DearmorCmd; -import sop.cli.picocli.commands.DecryptCmd; -import sop.cli.picocli.commands.InlineDetachCmd; -import sop.cli.picocli.commands.EncryptCmd; -import sop.cli.picocli.commands.ExtractCertCmd; -import sop.cli.picocli.commands.GenerateKeyCmd; -import sop.cli.picocli.commands.InlineSignCmd; -import sop.cli.picocli.commands.InlineVerifyCmd; -import sop.cli.picocli.commands.ListProfilesCmd; -import sop.cli.picocli.commands.RevokeKeyCmd; -import sop.cli.picocli.commands.SignCmd; -import sop.cli.picocli.commands.VerifyCmd; -import sop.cli.picocli.commands.VersionCmd; -import sop.exception.SOPGPException; - -import java.util.List; -import java.util.Locale; -import java.util.ResourceBundle; - -@CommandLine.Command( - name = "sop", - resourceBundle = "msg_sop", - exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, - subcommands = { - // Meta Subcommands - VersionCmd.class, - ListProfilesCmd.class, - // Key and Certificate Management Subcommands - GenerateKeyCmd.class, - ChangeKeyPasswordCmd.class, - RevokeKeyCmd.class, - ExtractCertCmd.class, - // Messaging Subcommands - SignCmd.class, - VerifyCmd.class, - EncryptCmd.class, - DecryptCmd.class, - InlineDetachCmd.class, - InlineSignCmd.class, - InlineVerifyCmd.class, - // Transport Subcommands - ArmorCmd.class, - DearmorCmd.class, - // Miscellaneous Subcommands - CommandLine.HelpCommand.class, - AutoComplete.GenerateCompletion.class - } -) -public class SopCLI { - // Singleton - static SOP SOP_INSTANCE; - static ResourceBundle cliMsg = ResourceBundle.getBundle("msg_sop"); - - public static String EXECUTABLE_NAME = "sop"; - - @CommandLine.Option(names = {"--stacktrace"}, - scope = CommandLine.ScopeType.INHERIT) - static boolean stacktrace; - - public static void main(String[] args) { - int exitCode = execute(args); - if (exitCode != 0) { - System.exit(exitCode); - } - } - - public static int execute(String[] args) { - - // Set locale - new CommandLine(new InitLocale()).parseArgs(args); - - // get error message bundle - cliMsg = ResourceBundle.getBundle("msg_sop"); - - // Prepare CLI - CommandLine cmd = new CommandLine(SopCLI.class); - - // explicitly set help command resource bundle - cmd.getSubcommands().get("help").setResourceBundle(ResourceBundle.getBundle("msg_help")); - - // Hide generate-completion command - cmd.getSubcommands().get("generate-completion").getCommandSpec().usageMessage().hidden(true); - - cmd.setCommandName(EXECUTABLE_NAME) - .setExecutionExceptionHandler(new SOPExecutionExceptionHandler()) - .setExitCodeExceptionMapper(new SOPExceptionExitCodeMapper()) - .setCaseInsensitiveEnumValuesAllowed(true); - - return cmd.execute(args); - } - - public static SOP getSop() { - if (SOP_INSTANCE == null) { - String errorMsg = cliMsg.getString("sop.error.runtime.no_backend_set"); - throw new IllegalStateException(errorMsg); - } - return SOP_INSTANCE; - } - - public static void setSopInstance(SOP instance) { - SOP_INSTANCE = instance; - } -} - -/** - * Control the locale. - * - * @see Picocli Readme - */ -class InitLocale { - @CommandLine.Option(names = { "-l", "--locale" }, descriptionKey = "sop.locale") - void setLocale(String locale) { - Locale.setDefault(new Locale(locale)); - } - - @CommandLine.Unmatched - List remainder; // ignore any other parameters and options in the first parsing phase -} diff --git a/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java b/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java deleted file mode 100644 index 83f426d..0000000 --- a/sop-java-picocli/src/main/java/sop/cli/picocli/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Implementation of the Stateless OpenPGP Command Line Interface using Picocli. - */ -package sop.cli.picocli; diff --git a/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt new file mode 100644 index 0000000..1d5d46b --- /dev/null +++ b/sop-java-picocli/src/main/kotlin/sop/cli/picocli/SopCLI.kt @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package sop.cli.picocli + +import java.util.* +import kotlin.system.exitProcess +import picocli.AutoComplete.GenerateCompletion +import picocli.CommandLine +import picocli.CommandLine.* +import sop.SOP +import sop.cli.picocli.commands.* +import sop.exception.SOPGPException + +@Command( + name = "sop", + resourceBundle = "msg_sop", + exitCodeOnInvalidInput = SOPGPException.UnsupportedSubcommand.EXIT_CODE, + subcommands = + [ + // Meta subcommands + VersionCmd::class, + ListProfilesCmd::class, + // Key and certificate management + GenerateKeyCmd::class, + ChangeKeyPasswordCmd::class, + RevokeKeyCmd::class, + ExtractCertCmd::class, + // Messaging subcommands + SignCmd::class, + VerifyCmd::class, + EncryptCmd::class, + DecryptCmd::class, + InlineDetachCmd::class, + InlineSignCmd::class, + InlineVerifyCmd::class, + // Transport + ArmorCmd::class, + DearmorCmd::class, + // misc + HelpCommand::class, + GenerateCompletion::class]) +class SopCLI { + + companion object { + @JvmStatic private var sopInstance: SOP? = null + + @JvmStatic + fun getSop(): SOP = + checkNotNull(sopInstance) { cliMsg.getString("sop.error.runtime.no_backend_set") } + + @JvmStatic + fun setSopInstance(sop: SOP?) { + sopInstance = sop + } + + @JvmField var cliMsg: ResourceBundle = ResourceBundle.getBundle("msg_sop") + + @JvmField var EXECUTABLE_NAME = "sop" + + @JvmField + @Option(names = ["--stacktrace"], scope = CommandLine.ScopeType.INHERIT) + var stacktrace = false + + @JvmStatic + fun main(vararg args: String) { + val exitCode = execute(*args) + if (exitCode != 0) { + exitProcess(exitCode) + } + } + + @JvmStatic + fun execute(vararg args: String): Int { + // Set locale + CommandLine(InitLocale()).parseArgs(*args) + + // Re-set bundle with updated locale + cliMsg = ResourceBundle.getBundle("msg_sop") + + return CommandLine(SopCLI::class.java) + .apply { + // explicitly set help command resource bundle + subcommands["help"]?.setResourceBundle(ResourceBundle.getBundle("msg_help")) + // Hide generate-completion command + subcommands["generate-completion"]?.commandSpec?.usageMessage()?.hidden(true) + // overwrite executable name + commandName = EXECUTABLE_NAME + // setup exception handling + executionExceptionHandler = SOPExecutionExceptionHandler() + exitCodeExceptionMapper = SOPExceptionExitCodeMapper() + isCaseInsensitiveEnumValuesAllowed = true + } + .execute(*args) + } + } + + /** + * Control the locale. + * + * @see Picocli Readme + */ + @Command + class InitLocale { + @Option(names = ["-l", "--locale"], descriptionKey = "sop.locale") + fun setLocale(locale: String) = Locale.setDefault(Locale(locale)) + + @Unmatched + var remainder: MutableList = + mutableListOf() // ignore any other parameters and options in the first parsing phase + } +} diff --git a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java index 47b6123..68b32be 100644 --- a/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java +++ b/sop-java-picocli/src/test/java/sop/cli/picocli/SOPTest.java @@ -45,7 +45,7 @@ public class SOPTest { @Test @ExpectSystemExitWithStatus(1) public void assertThrowsIfNoSOPBackendSet() { - SopCLI.SOP_INSTANCE = null; + SopCLI.setSopInstance(null); // At this point, no SOP backend is set, so an InvalidStateException triggers exit(1) SopCLI.main(new String[] {"armor"}); }