diff --git a/src/main/java/me/itzg/helpers/McImageHelper.java b/src/main/java/me/itzg/helpers/McImageHelper.java index 51502975..091d2c23 100644 --- a/src/main/java/me/itzg/helpers/McImageHelper.java +++ b/src/main/java/me/itzg/helpers/McImageHelper.java @@ -107,12 +107,6 @@ public class McImageHelper { //language=RegExp public static final String SPLIT_COMMA_NL = "\\n|\\s*,\\s*"; public static final String SPLIT_SYNOPSIS_COMMA_NL = ",|"; - /** - * Also works for newline delimited values. - */ - //language=RegExp - public static final String SPLIT_COMMA_WS = "\\s+|\\s*,\\s*"; - public static final String SPLIT_SYNOPSIS_COMMA_WS = ",|"; //language=RegExp public static final String VERSION_REGEX = "\\d+(\\.\\d+)+"; @@ -243,4 +237,5 @@ public Integer call() throws Exception { return ExitCode.OK; } } + } \ No newline at end of file diff --git a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java index d32b9aab..a60a3fd6 100644 --- a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java +++ b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesCommand.java @@ -2,6 +2,7 @@ import static me.itzg.helpers.curseforge.CurseForgeApiClient.*; import static me.itzg.helpers.curseforge.ModFileRefResolver.idsFrom; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeOptionList; import java.nio.file.Files; import java.nio.file.Path; @@ -13,6 +14,7 @@ import java.util.concurrent.Callable; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import me.itzg.helpers.McImageHelper; import me.itzg.helpers.cache.ApiCaching; import me.itzg.helpers.cache.ApiCachingDisabled; import me.itzg.helpers.cache.ApiCachingImpl; @@ -96,14 +98,19 @@ public void setSlugCategory(String defaultCategory) { @ArgGroup(exclusive = false) SharedFetchArgs sharedFetchArgs = new SharedFetchArgs(); - @Parameters(split = ",", paramLabel = "REF", + @Parameters(paramLabel = "REF", + split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL, description = "Can be |':'," + " |'@'," + " |," + " project page URL, file page URL," + " '@'" - + "%nIf not specified, any previous mod/plugin files are removed") - List modFileRefs; + + "%nIf not specified, any previous mod/plugin files are removed." + + "%Embedded comments are allowed") + public void setModFileRefs(List modFileRefs) { + this.modFileRefs = normalizeOptionList(modFileRefs); + } + private List modFileRefs = Collections.emptyList(); @Override public Integer call() throws Exception { @@ -114,7 +121,6 @@ public Integer call() throws Exception { final Map previousFiles = buildPreviousFilesFromManifest(oldManifest); final CurseForgeFilesManifest newManifest; - if (modFileRefs != null && !modFileRefs.isEmpty()) { try ( final ApiCaching apiCaching = disableApiCaching ? new ApiCachingDisabled() @@ -128,37 +134,23 @@ public Integer call() throws Exception { ) { newManifest = apiClient.loadCategoryInfo(Arrays.asList(CATEGORY_MC_MODS, CATEGORY_BUKKIT_PLUGINS)) - .flatMap(categoryInfo -> + .flatMap(categoryInfo -> - processModFileRefs(categoryInfo, previousFiles, apiClient) - .map(entries -> CurseForgeFilesManifest.builder() - .entries(entries) - .build())) + processModFileRefs(categoryInfo, previousFiles, apiClient) + .map(entries -> CurseForgeFilesManifest.builder() + .entries(entries) + .build())) .doOnError(InvalidApiKeyException.class, throwable -> ApiKeyHelper.logKeyIssues(log, apiKey)) .block(); } } else { - // nothing to install or requesting full cleanup newManifest = null; } - if (oldManifest != null) { - Manifests.cleanup(outputDir, - mapFilePathsFromEntries(oldManifest), - mapFilePathsFromEntries(newManifest), - s -> log.info("Removing old file {}", s) - ); - } - if (newManifest != null && newManifest.getEntries() != null - && !newManifest.getEntries().isEmpty() - ) { - Manifests.save(outputDir, CurseForgeFilesManifest.ID, newManifest); - } - else { - Manifests.remove(outputDir, CurseForgeFilesManifest.ID); - } + Manifests.cleanup(outputDir, oldManifest, newManifest, log); + Manifests.apply(outputDir, CurseForgeFilesManifest.ID, newManifest); return ExitCode.OK; } @@ -167,7 +159,7 @@ public Integer call() throws Exception { private Mono> processModFileRefs(CategoryInfo categoryInfo, Map previousFiles, CurseForgeApiClient apiClient ) { - if (modFileRefs == null || modFileRefs.isEmpty()) { + if (modFileRefs.isEmpty()) { return Mono.just(Collections.emptyList()); } @@ -289,11 +281,4 @@ private Map buildPreviousFilesFromManifest(CurseForgeFile : Collections.emptyMap(); } - private static List mapFilePathsFromEntries(CurseForgeFilesManifest oldManifest) { - return - oldManifest != null ? - oldManifest.entries.stream().map(FileEntry::getFilePath).collect(Collectors.toList()) - : null; - } - } diff --git a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesManifest.java b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesManifest.java index 493cdae9..cfc62669 100644 --- a/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesManifest.java +++ b/src/main/java/me/itzg/helpers/curseforge/CurseForgeFilesManifest.java @@ -1,6 +1,9 @@ package me.itzg.helpers.curseforge; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import lombok.Builder; import lombok.Data; import lombok.Getter; @@ -24,4 +27,12 @@ public static class FileEntry { } List entries; + + @Override + public Collection getFiles() { + return entries != null ? entries.stream() + .map(FileEntry::getFilePath) + .collect(Collectors.toList()) + : Collections.emptyList(); + } } diff --git a/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java b/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java index 6b0e65cd..e469e6fd 100644 --- a/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java +++ b/src/main/java/me/itzg/helpers/curseforge/InstallCurseForgeCommand.java @@ -1,6 +1,7 @@ package me.itzg.helpers.curseforge; import static me.itzg.helpers.http.Fetch.fetch; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeOptionSet; import java.io.BufferedWriter; import java.io.IOException; @@ -103,19 +104,24 @@ static class ExcludeIncludeArgs { PathOrUri excludeIncludeFile; static class Listed { - @Option(names = "--exclude-mods", paramLabel = "PROJECT_ID|SLUG", - split = McImageHelper.SPLIT_COMMA_WS, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_WS, + @Option(names = {"--exclude-mods", "--excludes"}, paramLabel = "PROJECT_ID|SLUG", + split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL, description = "For mods that need to be excluded from server deployments, such as those that don't label as client" ) - Set excludedMods; + public void setExcludedMods(Set excludedMods) { + this.excludedMods = normalizeOptionSet(excludedMods); + } + private Set excludedMods; - @Option(names = "--force-include-mods", paramLabel = "PROJECT_ID|SLUG", - split = McImageHelper.SPLIT_COMMA_WS, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_WS, + @Option(names = {"--force-include-mods", "--force-includes"}, paramLabel = "PROJECT_ID|SLUG", + split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL, description = "Some mods incorrectly declare client-only support, but still need to be included in a server deploy." + "%nThis can also be used to selectively override exclusions." ) + public void setForceIncludeMods(Set forceIncludeMods) { + this.forceIncludeMods = normalizeOptionSet(forceIncludeMods); + } Set forceIncludeMods; - } } diff --git a/src/main/java/me/itzg/helpers/files/Manifests.java b/src/main/java/me/itzg/helpers/files/Manifests.java index 9b813019..5bdc10e3 100644 --- a/src/main/java/me/itzg/helpers/files/Manifests.java +++ b/src/main/java/me/itzg/helpers/files/Manifests.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -59,11 +60,12 @@ public static void cleanup(Path baseDir, BaseManifest oldManifest, * @param oldManifest can be null * @param removeListener passed the path of a file being removed. Useful for debug logging as removed. */ - public static void cleanup(Path baseDir, BaseManifest oldManifest, BaseManifest newManifest, + public static void cleanup(Path baseDir, @Nullable BaseManifest oldManifest, @Nullable BaseManifest newManifest, Consumer removeListener ) throws IOException { if (oldManifest != null && oldManifest.getFiles() != null) { - cleanup(baseDir, oldManifest.getFiles(), newManifest.getFiles(), removeListener); + cleanup(baseDir, oldManifest.getFiles(), newManifest != null ? newManifest.getFiles() : Collections.emptyList(), + removeListener); } } @@ -158,12 +160,30 @@ public static Path buildManifestPath(Path outputDir, String id) { )); } - public static Path save(Path outputDir, String id, M manifest) { + /** + * Given a resulting ("new") manifest, it will save it when the file listing is non-empty or remove + * an existing manifest file if the listing is empty. + * @param outputDir directory to place the manifest file + * @param id manifest identifier, such as "fabric" + * @param manifest the resulting/new manifest + */ + public static void apply(Path outputDir, String id, M manifest) { + if (manifest == null) { + remove(outputDir, id); + } + else if (manifest.getFiles() == null || manifest.getFiles().isEmpty()) { + remove(outputDir, id); + } + else { + save(outputDir, id, manifest); + } + } + + public static void save(Path outputDir, String id, M manifest) { final Path manifestPath = buildManifestPath(outputDir, id); try { ObjectMappers.defaultMapper().writeValue(manifestPath.toFile(), manifest); - return manifestPath; } catch (IOException e) { throw new ManifestException("Failed to save manifest", e); } diff --git a/src/main/java/me/itzg/helpers/get/GetCommand.java b/src/main/java/me/itzg/helpers/get/GetCommand.java index 1b613d5b..058761ff 100644 --- a/src/main/java/me/itzg/helpers/get/GetCommand.java +++ b/src/main/java/me/itzg/helpers/get/GetCommand.java @@ -2,6 +2,7 @@ import static me.itzg.helpers.McImageHelper.OPTION_SPLIT_COMMAS; import static me.itzg.helpers.http.Fetch.fetch; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeStream; import java.io.IOException; import java.io.PrintWriter; @@ -271,9 +272,7 @@ private void readUris() throws IOException { final LenientUriConverter uriConverter = new LenientUriConverter(); - Files.readAllLines(urisFile).stream() - .filter(line -> !line.startsWith("#")) - .filter(line -> !line.trim().isEmpty()) + normalizeStream(Files.readAllLines(urisFile).stream()) .map(line -> { try { return uriConverter.convert(line); diff --git a/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java b/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java index eb9236d5..ff668c6a 100644 --- a/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java +++ b/src/main/java/me/itzg/helpers/modrinth/InstallModrinthModpackCommand.java @@ -1,7 +1,10 @@ package me.itzg.helpers.modrinth; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeOptionList; + import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import lombok.AccessLevel; @@ -90,14 +93,22 @@ public class InstallModrinthModpackCommand implements Callable { @Option(names = "--exclude-files", split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL, description = "Files to exclude, such as improperly declared client mods. It will match any part of the file's name/path." + + "%nEmbedded comments are allowed." ) - List excludeFiles; + public void setExcludeFiles(List excludeFiles) { + this.excludeFiles = normalizeOptionList(excludeFiles); + } + private List excludeFiles = Collections.emptyList(); @Option(names = "--force-include-files", split = McImageHelper.SPLIT_COMMA_NL, splitSynopsisLabel = McImageHelper.SPLIT_SYNOPSIS_COMMA_NL, description = "Files to force include that were marked as non-server mods. It will match any part of the file's name/path." + + "%nEmbedded comments are allowed." ) - List forceIncludeFiles; + public void setForceIncludeFiles(List forceIncludeFiles) { + this.forceIncludeFiles = normalizeOptionList(forceIncludeFiles); + } + private List forceIncludeFiles = Collections.emptyList(); @Option(names = "--overrides-exclusions", split = "\n|,", splitSynopsisLabel = "NL or ,", @@ -105,8 +116,12 @@ public class InstallModrinthModpackCommand implements Callable { + "* : matches any non-slash characters\n" + "** : matches any characters\n" + "? : matches one character" + + "%nEmbedded comments are allowed." ) - List overridesExclusions; + public void setOverridesExclusions(List overridesExclusions) { + this.overridesExclusions = normalizeOptionList(overridesExclusions); + } + private List overridesExclusions = Collections.emptyList(); @Option(names = "--default-exclude-includes", paramLabel = "FILE|URI", description = "A JSON file that contains global and per modpack exclude/include declarations. " @@ -159,7 +174,7 @@ public Integer call() throws IOException { sharedFetch ) ) - .setOverridesExclusions(overridesExclusions) + .setOverridesExclusions(normalizeOptionList(overridesExclusions)) .setMaxConcurrentDownloads(maxConcurrentDownloads) .processModpack(sharedFetch) .flatMap(installation -> { diff --git a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java index 82d9dac8..794001be 100644 --- a/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java +++ b/src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java @@ -3,6 +3,7 @@ import static me.itzg.helpers.McImageHelper.SPLIT_COMMA_NL; import static me.itzg.helpers.McImageHelper.SPLIT_SYNOPSIS_COMMA_NL; import static me.itzg.helpers.http.Fetch.fetch; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeOptionList; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; @@ -54,12 +55,16 @@ public class ModrinthCommand implements Callable { + "%nExamples: fabric-api, fabric:fabric-api, fabric:fabric-api:0.76.1+1.19.2," + " datapack:terralith, @/path/to/modrinth-mods.txt" + "%nValid release types: release, beta, alpha" - + "%nValid loaders: fabric, forge, paper, datapack, etc.", + + "%nValid loaders: fabric, forge, paper, datapack, etc." + + "%nEmbedded comments are allowed.", split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL, paramLabel = "[loader:]id|slug[:version]" ) - List projects; + public void setProjects(List projects) { + this.projects = normalizeOptionList(projects); + } + private List projects = Collections.emptyList(); @Option(names = "--game-version", description = "Applicable Minecraft version", required = true) String gameVersion; @@ -111,14 +116,7 @@ public enum DownloadDependencies { public Integer call() throws Exception { Files.createDirectories(outputDirectory); - // Unlike @Parameters, the argument of @Option could be a list with a single empty string - // when --projects="". So, trim away any blanks. - final List trimmedProjects = projects == null ? Collections.emptyList() : - projects.stream() - .filter(s -> !s.trim().isEmpty()) - .collect(Collectors.toList()); - - final List outputFiles = processProjects(trimmedProjects); + final List outputFiles = processProjects(projects); final ModrinthManifest newManifest = ModrinthManifest.builder() .files(Manifests.relativizeAll(outputDirectory, outputFiles)) @@ -127,13 +125,7 @@ public Integer call() throws Exception { final ModrinthManifest prevManifest = loadManifest(); Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); - - if (!outputFiles.isEmpty()) { - Manifests.save(outputDirectory, ModrinthManifest.ID, newManifest); - } - else { - Manifests.remove(outputDirectory, ModrinthManifest.ID); - } + Manifests.apply(outputDirectory, ModrinthManifest.ID, newManifest); return ExitCode.OK; } @@ -148,7 +140,6 @@ private List processProjects(List projects) { return modrinthApiClient.bulkGetProjects( expandProjectListings(projects).stream() - .filter(s -> !s.trim().isEmpty()) .map(ProjectRef::parse) ) .defaultIfEmpty(Collections.emptyList()) diff --git a/src/main/java/me/itzg/helpers/singles/MoreCollections.java b/src/main/java/me/itzg/helpers/singles/MoreCollections.java index b344070c..e02a8e7c 100644 --- a/src/main/java/me/itzg/helpers/singles/MoreCollections.java +++ b/src/main/java/me/itzg/helpers/singles/MoreCollections.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -18,11 +19,15 @@ public static Set makeNonNull(Set in) { } @SuppressWarnings("unused") - public static Set combine(Set in1, Set in2) { + public static List combineToList(Collection in1, Collection in2) { + return combineToStream(in1, in2) + .collect(Collectors.toList()); + } + + private static Stream combineToStream(Collection in1, Collection in2) { return Stream.of(in1, in2) .filter(Objects::nonNull) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + .flatMap(Collection::stream); } /** diff --git a/src/main/java/me/itzg/helpers/singles/NormalizeOptions.java b/src/main/java/me/itzg/helpers/singles/NormalizeOptions.java new file mode 100644 index 00000000..4f026cd0 --- /dev/null +++ b/src/main/java/me/itzg/helpers/singles/NormalizeOptions.java @@ -0,0 +1,41 @@ +package me.itzg.helpers.singles; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.Nullable; + +@UtilityClass +public class NormalizeOptions { + + public static List normalizeOptionList(@Nullable List values) { + if (values == null) { + return Collections.emptyList(); + } + + return normalizeStream(values.stream()) + .collect(Collectors.toList()); + } + + public static Set normalizeOptionSet(@Nullable Set values) { + if (values == null) { + return Collections.emptySet(); + } + + return normalizeStream(values.stream()) + .collect(Collectors.toSet()); + } + + public static Stream normalizeStream(Stream values) { + return values + .map(s -> { + final int pos = s.indexOf("#"); + return pos < 0 ? s : s.substring(0, pos); + }) + .map(String::trim) + .filter(s -> !s.isEmpty()); + } +} diff --git a/src/main/java/me/itzg/helpers/sync/MulitCopyCommand.java b/src/main/java/me/itzg/helpers/sync/MulitCopyCommand.java index 1aa7b038..2f31534c 100644 --- a/src/main/java/me/itzg/helpers/sync/MulitCopyCommand.java +++ b/src/main/java/me/itzg/helpers/sync/MulitCopyCommand.java @@ -2,6 +2,7 @@ import static me.itzg.helpers.McImageHelper.SPLIT_COMMA_NL; import static me.itzg.helpers.McImageHelper.SPLIT_SYNOPSIS_COMMA_NL; +import static me.itzg.helpers.singles.NormalizeOptions.normalizeOptionList; import java.io.IOException; import java.net.URI; @@ -11,6 +12,7 @@ import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import lombok.extern.slf4j.Slf4j; @@ -71,47 +73,49 @@ public class MulitCopyCommand implements Callable { @Option(names = "--ignore-missing-sources", description = "Don't log or fail exit code when any or all sources are missing") boolean ignoreMissingSources; - @Parameters(split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL, arity = "1..*", + @Parameters(split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL, paramLabel = "SRC", - description = "Any mix of source file, directory, or URLs." - + "%nCan be optionally comma or newline separated." + description = "Any mix of source file, directory, or URLs delimited by commas or newlines" + "%nPer-file destinations can be assigned by destination sources; + public void setSources(List sources) { + this.sources = normalizeOptionList(sources); + } + List sources = Collections.emptyList(); private final static String destinationDelimiter = "<"; @Override public Integer call() throws Exception { - Flux.fromIterable(sources) + final List results = Flux.fromIterable(sources) .map(String::trim) .filter(s -> !s.isEmpty()) .flatMap(source -> processSource(source, fileIsListingOption, dest)) .collectList() - .flatMap(this::cleanupAndSaveManifest) .block(); + cleanupAndSaveManifest(results); + return ExitCode.OK; } - private Mono cleanupAndSaveManifest(List paths) { - return Mono.justOrEmpty(manifestId) - .publishOn(Schedulers.boundedElastic()) - .map(s -> { - final MultiCopyManifest prevManifest = Manifests.load(dest, manifestId, MultiCopyManifest.class); + private void cleanupAndSaveManifest(List paths) { + if (manifestId != null) { + final MultiCopyManifest prevManifest = Manifests.load(dest, manifestId, MultiCopyManifest.class); - final MultiCopyManifest newManifest = MultiCopyManifest.builder() - .files(Manifests.relativizeAll(dest, paths)) - .build(); + final MultiCopyManifest newManifest = MultiCopyManifest.builder() + .files(Manifests.relativizeAll(dest, paths)) + .build(); - try { - Manifests.cleanup(dest, prevManifest, newManifest, log); - } catch (IOException e) { - log.warn("Failed to cleanup from previous manifest"); - } + try { + Manifests.cleanup(dest, prevManifest, newManifest, log); + } catch (IOException e) { + log.warn("Failed to cleanup from previous manifest"); + } - return Manifests.save(dest, manifestId, newManifest); - }); + Manifests.apply(dest, manifestId, newManifest); + } } private Publisher processSource(String source, boolean fileIsListing, Path parentDestination) { diff --git a/src/test/java/me/itzg/helpers/curseforge/CurseForgeFilesCommandTest.java b/src/test/java/me/itzg/helpers/curseforge/CurseForgeFilesCommandTest.java index c67c6d3a..93e4f224 100644 --- a/src/test/java/me/itzg/helpers/curseforge/CurseForgeFilesCommandTest.java +++ b/src/test/java/me/itzg/helpers/curseforge/CurseForgeFilesCommandTest.java @@ -85,13 +85,36 @@ void oneOfEachCategoryAndUpgrade() { "--api-base-url", wm.baseUrl(), "--api-key", "test", "--output-directory", tempDir.toString(), - "--default-category", "mc-mods" + "--default-category", "mc-mods", + "" ); assertThat(exitCode).isEqualTo(0); assertThat(tempDir.resolve("mods/jei-1.18.2-forge-10.2.1.1005.jar")).doesNotExist(); assertThat(tempDir.resolve("plugins/worldguard-bukkit-7.0.7-dist.jar")).doesNotExist(); + assertThat(Manifests.buildManifestPath(tempDir, CurseForgeFilesManifest.ID)).doesNotExist(); + } + + @Test + void multiLineOneWithComments() { + int exitCode = new CommandLine( + new CurseForgeFilesCommand() + ) + .execute( + "--api-base-url", wm.baseUrl(), + "--api-key", "test", + "--output-directory", tempDir.toString(), + "--default-category", "mc-mods", + "jei:4434385 # inline comment" + + "\n# Some comment" + + "\nhttps://www.curseforge.com/minecraft/bukkit-plugins/worldguard/files/3677516" + ); + + assertThat(exitCode).isEqualTo(0); + + assertThat(tempDir.resolve("mods/jei-1.18.2-forge-10.2.1.1003.jar")).exists(); + assertThat(tempDir.resolve("plugins/worldguard-bukkit-7.0.7-dist.jar")).exists(); } @Test diff --git a/src/test/java/me/itzg/helpers/fabric/FabricLauncherInstallerTest.java b/src/test/java/me/itzg/helpers/fabric/FabricLauncherInstallerTest.java index 2eec63a2..3407cea1 100644 --- a/src/test/java/me/itzg/helpers/fabric/FabricLauncherInstallerTest.java +++ b/src/test/java/me/itzg/helpers/fabric/FabricLauncherInstallerTest.java @@ -37,9 +37,8 @@ void testSaveManifest() { .files(Collections.singletonList("fabric-server-mc.1.19.2-loader.0.14.11-launcher.0.11.1.jar")) .build(); - final Path manifestPath = Manifests.save(tempDir, "fabric", manifest); - assertThat(manifestPath.toString()) - .contains("fabric"); + Manifests.save(tempDir, "fabric", manifest); + assertThat(tempDir.resolve(".fabric-manifest.json")).exists(); final FabricManifest loaded = Manifests.load(tempDir, "fabric", FabricManifest.class); diff --git a/src/test/java/me/itzg/helpers/sync/MulitCopyCommandTest.java b/src/test/java/me/itzg/helpers/sync/MulitCopyCommandTest.java index 3672f788..075acdcc 100644 --- a/src/test/java/me/itzg/helpers/sync/MulitCopyCommandTest.java +++ b/src/test/java/me/itzg/helpers/sync/MulitCopyCommandTest.java @@ -1,12 +1,10 @@ package me.itzg.helpers.sync; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.assertj.core.api.Assertions.assertThat; + import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import picocli.CommandLine; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -15,12 +13,16 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.assertj.core.api.Assertions.assertThat; +import me.itzg.helpers.files.Manifests; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; class MulitCopyCommandTest { + private final RandomStringUtils randomStringUtils = RandomStringUtils.insecure(); @TempDir Path tempDir; @@ -47,6 +49,47 @@ void one() throws IOException { .hasSameTextualContentAs(srcFile); } + @Test + void oneThenRemove() throws IOException { + final Path srcFile = writeLine(tempDir, "source.txt", "content"); + final Path destDir = tempDir.resolve("dest"); + final String manifestId = randomStringUtils.nextAlphabetic(10); + + { + final int exitCode = new CommandLine(new MulitCopyCommand()) + .execute( + "--to", destDir.toString(), + "--manifest-id", manifestId, + srcFile.toString() + ); + assertThat(exitCode).isEqualTo(CommandLine.ExitCode.OK); + + assertThat(destDir.resolve("source.txt")) + .exists() + .hasSameTextualContentAs(srcFile); + + assertThat(Manifests.buildManifestPath(destDir, manifestId)).exists(); + } + + { + final int exitCode = new CommandLine(new MulitCopyCommand()) + .execute( + "--to", destDir.toString(), + "--manifest-id", manifestId + // no entries + ); + assertThat(exitCode).isEqualTo(CommandLine.ExitCode.OK); + + assertThat(destDir.resolve("source.txt")) + .doesNotExist(); + + assertThat(Manifests.buildManifestPath(destDir, manifestId)) + .doesNotExist(); + } + + + } + @Test void oneWithInlineDestination() throws IOException { final Path srcFile = writeLine(tempDir, "source2.txt", "content");