Kotlin conversion: SopCLI

This commit is contained in:
Paul Schaub 2023-11-15 12:35:23 +01:00
parent 0c2cf5cb19
commit baa44a6b1a
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 114 additions and 138 deletions

View file

@ -1,129 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 <a href="https://picocli.info/#_controlling_the_locale">Picocli Readme</a>
*/
class InitLocale {
@CommandLine.Option(names = { "-l", "--locale" }, descriptionKey = "sop.locale")
void setLocale(String locale) {
Locale.setDefault(new Locale(locale));
}
@CommandLine.Unmatched
List<String> remainder; // ignore any other parameters and options in the first parsing phase
}

View file

@ -1,8 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Implementation of the Stateless OpenPGP Command Line Interface using Picocli.
*/
package sop.cli.picocli;

View file

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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 <a href="https://picocli.info/#_controlling_the_locale">Picocli Readme</a>
*/
@Command
class InitLocale {
@Option(names = ["-l", "--locale"], descriptionKey = "sop.locale")
fun setLocale(locale: String) = Locale.setDefault(Locale(locale))
@Unmatched
var remainder: MutableList<String> =
mutableListOf() // ignore any other parameters and options in the first parsing phase
}
}

View file

@ -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"});
}