Make picocli fully handle config validation

This commit is contained in:
RatzzFatzz
2025-12-04 23:20:49 +01:00
parent 5eca28ecb9
commit 181c718e7a
13 changed files with 116 additions and 46 deletions

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger; package at.pcgamingfreaks.mkvaudiosubtitlechanger;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig; import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation.ValidationExecutionStrategy;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CachedMkvFileProcessor; import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CachedMkvFileProcessor;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.AttributeUpdaterKernel; import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.AttributeUpdaterKernel;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.CoherentAttributeUpdaterKernel; import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.CoherentAttributeUpdaterKernel;
@@ -35,7 +36,10 @@ public class Main implements Runnable {
if (args.length == 0) { if (args.length == 0) {
args = new String[] { "--help" }; args = new String[] { "--help" };
} }
new CommandLine(Main.class).execute(args);
new CommandLine(Main.class)
.setExecutionStrategy(new ValidationExecutionStrategy())
.execute(args);
} }
@Override @Override

View File

@@ -4,6 +4,7 @@ import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation.ValidFile;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation.ValidMkvToolNix; import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation.ValidMkvToolNix;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig; import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix; import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileUtils;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ValidationUtil; import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ValidationUtil;
import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator; import jakarta.validation.Validator;
@@ -108,20 +109,6 @@ public class InputConfig {
public static void setInstance(InputConfig c) { public static void setInstance(InputConfig c) {
config = c; config = c;
validate();
}
private static void validate() {
Validator validator = ValidationUtil.getValidator();
Set<ConstraintViolation<InputConfig>> violations = validator.validate(config);
if (!violations.isEmpty()) {
StringBuilder errors = new StringBuilder();
for (ConstraintViolation<InputConfig> violation : violations) {
errors.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("\n");
}
throw new CommandLine.ParameterException(config.getSpec().commandLine(), errors.toString());
}
} }
/** /**
@@ -129,13 +116,7 @@ public class InputConfig {
* @return absolute path to desired application. * @return absolute path to desired application.
*/ */
public String getPathFor(MkvToolNix application) { public String getPathFor(MkvToolNix application) {
return mkvToolNix.getAbsolutePath().endsWith("/") return FileUtils.getPathFor(mkvToolNix, application).getAbsolutePath();
? mkvToolNix.getAbsolutePath() + application
: mkvToolNix.getAbsolutePath() + "/" + application;
}
public String getPathFor(MkvToolNix application, boolean isWindows) {
return getPathFor(application) + (isWindows ? ".exe" : "");
} }
public String getNormalizedLibraryPath() { public String getNormalizedLibraryPath() {

View File

@@ -1,14 +1,12 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix; import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.SystemUtils;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileUtils.getPathFor;
public class ValidMkvToolNixValidator implements ConstraintValidator<ValidMkvToolNix, File> { public class ValidMkvToolNixValidator implements ConstraintValidator<ValidMkvToolNix, File> {
@Override @Override
@@ -18,7 +16,9 @@ public class ValidMkvToolNixValidator implements ConstraintValidator<ValidMkvToo
@Override @Override
public boolean isValid(File file, ConstraintValidatorContext context) { public boolean isValid(File file, ConstraintValidatorContext context) {
return file != null && file.exists() return file != null && file.exists()
&& Path.of(InputConfig.getInstance().getPathFor(MkvToolNix.MKV_MERGE, SystemUtils.IS_OS_WINDOWS)).toFile().exists() && getPathFor(file, MkvToolNix.MKV_MERGE).exists()
&& Path.of(InputConfig.getInstance().getPathFor(MkvToolNix.MKV_PROP_EDIT, SystemUtils.IS_OS_WINDOWS)).toFile().exists(); && getPathFor(file, MkvToolNix.MKV_PROP_EDIT).exists();
} }
} }

View File

@@ -0,0 +1,31 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ValidationUtil;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import picocli.CommandLine;
import java.util.Set;
public class ValidationExecutionStrategy implements CommandLine.IExecutionStrategy {
public int execute(CommandLine.ParseResult parseResult) {
validate(parseResult.commandSpec());
return new CommandLine.RunLast().execute(parseResult); // default execution strategy
}
private static void validate(CommandLine.Model.CommandSpec spec) {
Validator validator = ValidationUtil.getValidator();
Set<ConstraintViolation<InputConfig>> violations = validator.validate(((Main)spec.userObject()).getConfig());
if (!violations.isEmpty()) {
StringBuilder errors = new StringBuilder();
for (ConstraintViolation<InputConfig> violation : violations) {
errors.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("\n");
}
throw new CommandLine.ParameterException(spec.commandLine(), errors.toString());
}
}
}

View File

@@ -0,0 +1,21 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
import org.apache.commons.lang3.SystemUtils;
import java.io.File;
import java.nio.file.Path;
public class FileUtils {
private static final boolean isWindows = SystemUtils.IS_OS_WINDOWS;
private static String expandPath(File dir, MkvToolNix application) {
return dir.getAbsolutePath().endsWith("/")
? dir.getAbsolutePath() + application
: dir.getAbsolutePath() + "/" + application;
}
public static File getPathFor(File dir, MkvToolNix application) {
return Path.of(expandPath(dir, application) + (isWindows ? ".exe" : "")).toFile();
}
}

View File

@@ -1,15 +1,25 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validation.ValidationExecutionStrategy;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig; import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import picocli.CommandLine; import picocli.CommandLine;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE; import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TestUtil.args;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class ConfigTest { class InputConfigTest {
private static final String TEST_INVALID_DIR = "src/test/resources/test-dir";
@Test @Test
void initConfig() { void initConfig() {
@@ -42,4 +52,31 @@ class ConfigTest {
assertNull(InputConfig.getInstance().getConfigPath()); assertNull(InputConfig.getInstance().getConfigPath());
} }
private static Stream<Arguments> jakartaValidationData() {
return Stream.of(
Arguments.of(new String[]{"-l", "/arstarstarst", "-a", "jpn:ger"}, "libraryPath does not exist"),
Arguments.of(args("-m", TEST_INVALID_DIR), "mkvToolNix does not exist"),
Arguments.of(args("-t", "0"), "threads must be greater than or equal to 1"),
Arguments.of(args("-t", "-1"), "threads must be greater than or equal to 1"),
Arguments.of(args("-c", "-1"), "coherent must be greater than or equal to 0")
);
}
@ParameterizedTest
@MethodSource("jakartaValidationData")
void testJakartaValidation(String[] args, String expectedMessage) {
InputConfig.getInstance(true);
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
new CommandLine(Main.class)
.setExecutionStrategy(new ValidationExecutionStrategy())
.setErr(printWriter)
.execute(args);
printWriter.flush();
assertTrue(writer.toString().contains(expectedMessage));
}
} }

View File

@@ -1,4 +1,4 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig; import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
@@ -38,13 +39,5 @@ class IntegerConfigParameterTest {
Main sut = new Main(); Main sut = new Main();
assertThrows(CommandLine.MissingParameterException.class, () -> CommandLine.populateCommand(sut, args("-t"))); assertThrows(CommandLine.MissingParameterException.class, () -> CommandLine.populateCommand(sut, args("-t")));
assertThrows(CommandLine.MissingParameterException.class, () -> CommandLine.populateCommand(sut, args("--threads"))); assertThrows(CommandLine.MissingParameterException.class, () -> CommandLine.populateCommand(sut, args("--threads")));
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
CommandLine underTest = new CommandLine(sut);
underTest = underTest.setErr(printWriter);
underTest.execute(args("-t", "0"));
printWriter.flush();
assertTrue(writer.toString().contains("threads must be greater than or equal to 1"));
} }
} }

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
@@ -18,7 +19,6 @@ import static org.junit.jupiter.api.Assertions.*;
class MkvToolNixPathConfigParameterTest { class MkvToolNixPathConfigParameterTest {
private static final String TEST_INVALID_DIR = "src/test/resources/test-dir";
private static final String TEST_MKVTOOLNIX_DIR = SystemUtils.IS_OS_WINDOWS ? "src/test/resources/mkvtoolnix_exe" : "src/test/resources/mkvtoolnix"; private static final String TEST_MKVTOOLNIX_DIR = SystemUtils.IS_OS_WINDOWS ? "src/test/resources/mkvtoolnix_exe" : "src/test/resources/mkvtoolnix";
private static Stream<Arguments> provideTestCases() { private static Stream<Arguments> provideTestCases() {
@@ -39,7 +39,6 @@ class MkvToolNixPathConfigParameterTest {
@Test @Test
void validate() { void validate() {
Main sut = new Main(); Main sut = new Main();
// assertThrows(CommandLine.ParameterException.class, () -> new CommandLine(sut).execute(args("-m", TEST_INVALID_DIR)));
assertThrows(CommandLine.ParameterException.class, () -> CommandLine.populateCommand(sut, args("-m"))); assertThrows(CommandLine.ParameterException.class, () -> CommandLine.populateCommand(sut, args("-m")));
assertThrows(CommandLine.ParameterException.class, () -> CommandLine.populateCommand(sut, args(""))); assertThrows(CommandLine.ParameterException.class, () -> CommandLine.populateCommand(sut, args("")));
} }

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;

View File

@@ -1,6 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config; package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.fields;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main; import at.pcgamingfreaks.mkvaudiosubtitlechanger.Main;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.InputConfig;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;