Skip to content

Commit 5475ed9

Browse files
add prefix system
1 parent ae21a2b commit 5475ed9

File tree

5 files changed

+105
-85
lines changed

5 files changed

+105
-85
lines changed

src/main/java/me/itzg/helpers/modrinth/ModrinthApiClient.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,8 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
130130
@Nullable Loader loader, String gameVersion,
131131
VersionType defaultVersionType) {
132132

133-
final Loader loaderToQuery = projectRef.isDatapack() ? Loader.datapack : loader;
134-
135133
if (projectRef.hasVersionName()) {
136-
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
134+
return getVersionsForProject(project.getId(), loader, gameVersion)
137135
.flatMap(versions ->
138136
Mono.justOrEmpty(versions.stream()
139137
.filter(version ->
@@ -144,7 +142,7 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
144142
));
145143
}
146144
if (projectRef.hasVersionType()) {
147-
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
145+
return getVersionsForProject(project.getId(), loader, gameVersion)
148146
.mapNotNull(versions -> pickVersion(project, versions, projectRef.getVersionType()));
149147
} else if (projectRef.hasVersionId()) {
150148
return getVersionFromId(projectRef.getVersionId())
@@ -153,7 +151,7 @@ public Mono<Version> resolveProjectVersion(Project project, ProjectRef projectRe
153151
projectRef.getVersionId(), project.getSlug()))
154152
);
155153
} else {
156-
return getVersionsForProject(project.getId(), loaderToQuery, gameVersion)
154+
return getVersionsForProject(project.getId(), loader, gameVersion)
157155
.mapNotNull(versions -> pickVersion(project, versions, defaultVersionType));
158156
}
159157
}

src/main/java/me/itzg/helpers/modrinth/ModrinthCommand.java

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
public class ModrinthCommand implements Callable<Integer> {
4545

4646
public static final String DATAPACKS_SUBDIR = "datapacks";
47-
@Option(names = "--projects", description = "Project ID or Slug",
47+
@Option(names = "--projects", description = "Project ID or Slug. Prefix with loader: e.g. fabric:project-id",
4848
split = SPLIT_COMMA_NL, splitSynopsisLabel = SPLIT_SYNOPSIS_COMMA_NL,
49-
paramLabel = "id|slug"
49+
paramLabel = "[loader:]id|slug"
5050
)
5151
List<String> projects;
5252

@@ -157,7 +157,7 @@ private ModrinthManifest loadManifest() throws IOException {
157157
return Manifests.load(outputDirectory, ModrinthManifest.ID, ModrinthManifest.class);
158158
}
159159

160-
private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient, Project project, Version version) {
160+
private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient, Loader loader, String gameVersion, Project project, Version version) {
161161
log.debug("Expanding dependencies of version={}", version);
162162
return version.getDependencies().stream()
163163
.filter(this::filterDependency)
@@ -170,7 +170,7 @@ private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient,
170170
if (dep.getVersionId() == null) {
171171
log.debug("Fetching versions of dep={} and picking", dep);
172172
depVersion = pickVersion(
173-
getVersionsForProject(modrinthApiClient, dep.getProjectId())
173+
getVersionsForProject(modrinthApiClient, dep.getProjectId(), loader, gameVersion)
174174
);
175175
}
176176
else {
@@ -193,7 +193,7 @@ private Stream<Version> expandDependencies(ModrinthApiClient modrinthApiClient,
193193
log.debug("Resolved version={} for dep={}", depVersion.getVersionNumber(), dep);
194194
return Stream.concat(
195195
Stream.of(depVersion),
196-
expandDependencies(modrinthApiClient, project, depVersion)
196+
expandDependencies(modrinthApiClient, loader, gameVersion, project, depVersion)
197197
)
198198
.peek(expandedVer -> log.debug("Expanded dependency={} into version={}", dep, expandedVer));
199199
}
@@ -229,16 +229,14 @@ private Version pickVersion(List<Version> versions, VersionType versionType) {
229229
return null;
230230
}
231231

232-
private Path download(boolean isDatapack, VersionFile versionFile) {
232+
private Path download(Loader loaderRef, VersionFile versionFile) {
233233
final Path outPath;
234234
try {
235-
if (!isDatapack) {
236-
outPath = Files.createDirectories(outputDirectory
237-
.resolve(loader.getType())
238-
)
239-
.resolve(versionFile.getFilename());
240-
}
241-
else {
235+
final Loader effectiveLoader = loaderRef != null ? loaderRef : loader;
236+
final String outputType = effectiveLoader.getType();
237+
238+
if (outputType == null) {
239+
// Datapack case
242240
if (worldDirectory.isAbsolute()) {
243241
outPath = Files.createDirectories(worldDirectory
244242
.resolve(DATAPACKS_SUBDIR)
@@ -253,9 +251,15 @@ private Path download(boolean isDatapack, VersionFile versionFile) {
253251
.resolve(versionFile.getFilename());
254252
}
255253
}
254+
else {
255+
outPath = Files.createDirectories(outputDirectory
256+
.resolve(outputType)
257+
)
258+
.resolve(versionFile.getFilename());
259+
}
256260

257261
} catch (IOException e) {
258-
throw new RuntimeException("Creating mods directory", e);
262+
throw new RuntimeException("Creating output directory", e);
259263
}
260264

261265
try {
@@ -267,11 +271,11 @@ private Path download(boolean isDatapack, VersionFile versionFile) {
267271
.handleStatus(Fetch.loggingDownloadStatusHandler(log))
268272
.execute();
269273
} catch (IOException e) {
270-
throw new RuntimeException("Downloading mod file", e);
274+
throw new RuntimeException("Downloading file", e);
271275
}
272276
}
273277

274-
private List<Version> getVersionsForProject(ModrinthApiClient modrinthApiClient, String project) {
278+
private List<Version> getVersionsForProject(ModrinthApiClient modrinthApiClient, String project, Loader loader, String gameVersion) {
275279
final List<Version> versions = modrinthApiClient.getVersionsForProject(
276280
project, loader, gameVersion
277281
)
@@ -294,10 +298,12 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
294298
log.debug("Starting with project='{}' slug={}", project.getTitle(), project.getSlug());
295299

296300
if (projectsProcessed.add(project.getId())) {
301+
final Loader effectiveLoader = projectRef.getLoaderRef() != null ? projectRef.getLoaderRef() : loader;
302+
297303
final Version version;
298304
try {
299305
version = modrinthApiClient.resolveProjectVersion(
300-
project, projectRef, loader, gameVersion, defaultVersionType
306+
project, projectRef, effectiveLoader, gameVersion, defaultVersionType
301307
)
302308
.block();
303309
} catch (NoApplicableVersionsException | NoFilesAvailableException e) {
@@ -309,33 +315,24 @@ private Stream<Path> processProject(ModrinthApiClient modrinthApiClient, Project
309315
throw new GenericException(String.format("Project %s has no files declared", project.getSlug()));
310316
}
311317

312-
final boolean isDatapack = isDatapack(version);
313-
314318
return Stream.concat(
315319
Stream.of(version),
316-
expandDependencies(modrinthApiClient, project, version)
320+
expandDependencies(modrinthApiClient, effectiveLoader, gameVersion, project, version)
317321
)
318322
.map(ModrinthApiClient::pickVersionFile)
319-
.map(versionFile -> download(isDatapack, versionFile))
320-
.flatMap(downloadedFile -> !isDatapack ? expandIfZip(downloadedFile) : Stream.empty());
323+
.map(versionFile -> download(effectiveLoader, versionFile))
324+
.flatMap(downloadedFile -> expandIfZip(downloadedFile));
321325
}
322326
else {
323327
throw new InvalidParameterException(
324328
String.format("Project %s does not have any matching versions for loader %s, game version %s",
325-
projectRef, loader, gameVersion
329+
projectRef, effectiveLoader, gameVersion
326330
));
327331
}
328332
}
329333
return Stream.empty();
330334
}
331335

332-
private boolean isDatapack(Version version) {
333-
return
334-
version.getLoaders() != null
335-
&& version.getLoaders().size() == 1
336-
&& version.getLoaders().get(0).equals(Constants.LOADER_DATAPACK);
337-
}
338-
339336
/**
340337
* If downloadedFile ends in .zip, then expand it, return its files and given file.
341338
*

src/main/java/me/itzg/helpers/modrinth/ProjectRef.java

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
import java.nio.file.Files;
88
import java.nio.file.Path;
99
import java.nio.file.Paths;
10+
import java.util.Arrays;
11+
import java.util.Set;
1012
import java.util.regex.Matcher;
1113
import java.util.regex.Pattern;
14+
import java.util.stream.Collectors;
1215
import lombok.Getter;
1316
import lombok.ToString;
1417
import me.itzg.helpers.errors.InvalidParameterException;
@@ -22,10 +25,13 @@ public class ProjectRef {
2225
private static final Pattern MODPACK_PAGE_URL = Pattern.compile(
2326
"https://modrinth.com/modpack/(?<slug>.+?)(/version/(?<versionName>.+))?"
2427
);
25-
private static final Pattern PROJECT_REF = Pattern.compile("(?<datapack>datapack:)?(?<idSlug>[^:]+?)(:(?<version>[^:]+))?");
28+
private static final Set<String> VALID_LOADERS = Arrays.stream(Loader.values())
29+
.map(Enum::name)
30+
.map(String::toLowerCase)
31+
.collect(Collectors.toSet());
2632

2733
private final String idOrSlug;
28-
private final boolean datapack;
34+
private final Loader loaderRef;
2935

3036
/**
3137
* Either a remote URI or a file URI for a locally provided file
@@ -36,32 +42,48 @@ public class ProjectRef {
3642
private final String versionNumber;
3743

3844
public static ProjectRef parse(String projectRef) {
39-
final Matcher m = PROJECT_REF.matcher(projectRef);
40-
if (!m.matches()) {
41-
throw new InvalidParameterException("Invalid project reference: " + projectRef);
45+
// First, try to split into potential loader prefix and the rest
46+
final int firstColon = projectRef.indexOf(':');
47+
Loader loaderRef = null;
48+
String rest = projectRef;
49+
50+
if (firstColon > 0) {
51+
final String prefix = projectRef.substring(0, firstColon);
52+
if (VALID_LOADERS.contains(prefix.toLowerCase())) {
53+
loaderRef = Loader.valueOf(prefix);
54+
rest = projectRef.substring(firstColon + 1);
55+
}
56+
}
57+
58+
// Now process the rest of the string
59+
final int versionSeparator = rest.indexOf(':');
60+
String idSlug;
61+
String version = null;
62+
63+
if (versionSeparator >= 0) {
64+
idSlug = rest.substring(0, versionSeparator);
65+
version = rest.substring(versionSeparator + 1);
66+
} else {
67+
idSlug = rest;
4268
}
4369

44-
return new ProjectRef(
45-
m.group("idSlug"),
46-
m.group("version"),
47-
m.group("datapack") != null
48-
);
70+
return new ProjectRef(idSlug, version, loaderRef);
4971
}
5072

5173
/**
5274
*
5375
* @param version can be a {@link VersionType}, ID, or name/number
5476
*/
5577
public ProjectRef(String projectSlug, String version) {
56-
this(projectSlug, version, false);
78+
this(projectSlug, version, null);
5779
}
5880

5981
/**
6082
* @param version can be a {@link VersionType}, ID, or name/number
6183
*/
62-
public ProjectRef(String projectSlug, @Nullable String version, boolean datapack) {
84+
public ProjectRef(String projectSlug, @Nullable String version, Loader loaderRef) {
6385
this.idOrSlug = projectSlug;
64-
this.datapack = datapack;
86+
this.loaderRef = loaderRef;
6587
this.projectUri = null;
6688
this.versionType = parseVersionType(version);
6789
if (this.versionType == null) {
@@ -81,7 +103,7 @@ public ProjectRef(String projectSlug, @Nullable String version, boolean datapack
81103
}
82104

83105
public ProjectRef(URI projectUri, String versionId) {
84-
this.datapack = false;
106+
this.loaderRef = null;
85107
this.projectUri = projectUri;
86108

87109
final String filename = extractFilename(projectUri);
@@ -103,37 +125,40 @@ public static ProjectRef fromPossibleUrl(
103125
String possibleUrl, String defaultVersion)
104126
{
105127
// First, see if it is a modrinth page URL
106-
107-
final Matcher m = MODPACK_PAGE_URL.matcher(possibleUrl);
108-
if(m.matches()) {
109-
String projectSlug = m.group("slug");
110-
String projectVersion = m.group("versionName") != null ?
111-
m.group("versionName") : defaultVersion;
112-
return new ProjectRef(projectSlug, projectVersion);
113-
} else {
114-
try {
115-
// Might be custom URL, local file, or slug
116-
// ...try as a (remote or file) URL first
117-
return new ProjectRef(
118-
new URL(possibleUrl).toURI(), defaultVersion
119-
);
120-
} catch(MalformedURLException | URISyntaxException e) {
121-
// Not a valid URL, so
122-
// narrow down if it is a file path by looking at suffix
123-
if (possibleUrl.endsWith(".mrpack")) {
124-
final Path path = Paths.get(possibleUrl);
125-
if (!Files.exists(path)) {
126-
throw new InvalidParameterException("Given modrinth project looks like a file, but doesn't exist");
127-
}
128-
128+
try {
129+
final Matcher m = MODPACK_PAGE_URL.matcher(possibleUrl);
130+
if(m.matches()) {
131+
String projectSlug = m.group("slug");
132+
String projectVersion = m.group("versionName") != null ?
133+
m.group("versionName") : defaultVersion;
134+
return new ProjectRef(projectSlug, projectVersion);
135+
} else {
136+
try {
137+
// Might be custom URL, local file, or slug
138+
// ...try as a (remote or file) URL first
129139
return new ProjectRef(
130-
path.toUri(),
131-
defaultVersion
140+
new URL(possibleUrl).toURI(), defaultVersion
132141
);
133-
}
142+
} catch(MalformedURLException | URISyntaxException e) {
143+
// Not a valid URL, so
144+
// narrow down if it is a file path by looking at suffix
145+
if (possibleUrl.endsWith(".mrpack")) {
146+
final Path path = Paths.get(possibleUrl);
147+
if (!Files.exists(path)) {
148+
throw new InvalidParameterException("Given modrinth project looks like a file, but doesn't exist");
149+
}
150+
151+
return new ProjectRef(
152+
path.toUri(),
153+
defaultVersion
154+
);
155+
}
134156

135-
return new ProjectRef(possibleUrl, defaultVersion);
157+
return new ProjectRef(possibleUrl, defaultVersion);
158+
}
136159
}
160+
} catch (Exception e) {
161+
throw new InvalidParameterException("Invalid project reference: " + possibleUrl, e);
137162
}
138163
}
139164

src/test/java/me/itzg/helpers/modrinth/ModrinthCommandTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ void handlesDatapacksSpecificVersion(boolean absoluteWorldDir, @TempDir Path tem
311311
tempDir.resolve(worldDir).toString()
312312
: worldDir,
313313
"--game-version", "1.21.1",
314-
"--loader", "datapack",
314+
"--loader", "paper",
315315
"--projects", String.format("datapack:%s:%s", projectId, versionId)
316316
);
317317

@@ -496,4 +496,4 @@ private static void setupStubs() {
496496
)
497497
);
498498
}
499-
}
499+
}

src/test/java/me/itzg/helpers/modrinth/ProjectRefTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,22 @@ void constructorPullsProjectSlugFromFileURI(String input) {
139139

140140
@ParameterizedTest
141141
@MethodSource("parseProjectRef_parameters")
142-
void parseProjectRef(String input, String slugId, VersionType versionType, String versionId, String versionName, boolean datapack) {
142+
void parseProjectRef(String input, String slugId, VersionType versionType, String versionId, String versionName, Loader loaderRef) {
143143
final ProjectRef result = ProjectRef.parse(input);
144144
assertThat(result.getIdOrSlug()).isEqualTo(slugId);
145145
assertThat(result.getVersionType()).isEqualTo(versionType);
146146
assertThat(result.getVersionId()).isEqualTo(versionId);
147147
assertThat(result.getVersionNumber()).isEqualTo(versionName);
148-
assertThat(result.isDatapack()).isEqualTo(datapack);
148+
assertThat(result.getLoaderRef()).isEqualTo(loaderRef);
149149
}
150150

151151
public static Stream<Arguments> parseProjectRef_parameters() {
152152
return Stream.of(
153-
argumentSet("just slugId","terralith", "terralith", null, null, null, false),
154-
argumentSet("datapack","datapack:terralith", "terralith", null, null, null, true),
155-
argumentSet("with version ID","terralith:rEF3UnUI", "terralith", null, "rEF3UnUI", null, false),
156-
argumentSet("with version type","terralith:release", "terralith", VersionType.release, null, null, false),
157-
argumentSet("with version name","terralith:2.5.5", "terralith", null, null, "2.5.5", false)
153+
argumentSet("just slugId","terralith", "terralith", null, null, null, null),
154+
argumentSet("with loader prefix","fabric:terralith", "terralith", null, null, null, Loader.fabric),
155+
argumentSet("with loader and version ID","paper:terralith:rEF3UnUI", "terralith", null, "rEF3UnUI", null, Loader.paper),
156+
argumentSet("with loader and version type","datapack:terralith:release", "terralith", VersionType.release, null, null, Loader.datapack),
157+
argumentSet("with loader and version name","forge:terralith:2.5.5", "terralith", null, null, "2.5.5", Loader.forge)
158158
);
159159
}
160160
}

0 commit comments

Comments
 (0)