Add support for date/time conditions, closes #18

This commit is contained in:
Johannes Frohnmeyer 2022-06-07 14:49:59 +02:00
parent ac4d8efe3e
commit 44f40fd259
Signed by: Johannes
GPG Key ID: E76429612C2929F4
5 changed files with 162 additions and 21 deletions

View File

@ -26,3 +26,19 @@ Respackopts also allows checking mod (or minecraft) versions against predicates
Fabric API provides a system called resource conditions, which is similar to respackopts own system.
Respackopts previously allowed accessing fabric resource conditions and respackopts conditions from one another,
however, this feature was removed in favor of enforcing the new muscript syntax.
## Dates
Respackopts supports expressing conditions on the date of resource loading (not necessarily the date the resource is used)
You can get the current date via `date.today` and compare it (`==`, `>`, `<`, ...) with a date object created
via the date constructor, for example: `date(2022, 5, 4)` stands for the fourth of may 2022.
You can also access the year, month or day using `.year`, `.month` or `.day` respectively (for example, `date.today.year == 2022`)
## Times
Respackopts supports expressing conditions on the time of resource loading (not necessarily the time the resource is used)
You can get the current time via `time.now` and compare it (`==`, `>`, `<`, ...) with a time object created
via the time constructor, for example: `date(23, 30, 1)` stands for half past 11 PM and one second
You can also access the hour, minute or second using `.hour`, `.minute` or `.second` respectively (for example, `time.now.hour >= 21`)

View File

@ -132,28 +132,12 @@ public class MetaCache {
}
public static ExpressionParameter getParameter(@Nullable CacheKey key) {
return hydrateParameter(key == null ? new ExpressionParameter() : MetaCache.getState(key).expressionParameter());
}
public static ExpressionParameter hydrateParameter(ExpressionParameter parameter) {
ExpressionParameter parameter = key == null ? new ExpressionParameter() : MetaCache.getState(key).expressionParameter();
MetaCache.forEach((id, state) -> {
String key = Respackopts.sanitizeString(state.packId());
if (!parameter.has(key))
parameter.set(key, state.configBranch().getDynamic());
String packId = Respackopts.sanitizeString(state.packId());
if (!parameter.has(packId))
parameter.set(packId, state.configBranch().getDynamic());
});
StandardLib.addTo(parameter);
parameter.set("version", DFinal.of(args -> {
if (args.size() != 2) throw new IllegalArgumentException("Expected 2 arguments on version but got " + args.size());
VersionPredicate predicate;
try {
predicate = VersionPredicate.parse(args.get(1).asString().getValue());
} catch (VersionParsingException e) {
throw new RuntimeException("Could not parse version predicate", e);
}
return DFinal.of(FabricLoader.getInstance().getModContainer(args.get(0).asString().getValue())
.map(c -> predicate.test(c.getMetadata().getVersion()))
.orElse(false));
}));
return parameter;
}

View File

@ -0,0 +1,105 @@
package io.gitlab.jfronny.respackopts.util;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import net.fabricmc.loader.api.*;
import net.fabricmc.loader.api.metadata.version.*;
import java.time.*;
import java.util.*;
import java.util.function.*;
public class MuUtils {
public static ExpressionParameter addDefault(ExpressionParameter parameter) {
StandardLib.addTo(parameter);
parameter.set("version", DFinal.of(args -> {
if (args.size() != 2) throw new IllegalArgumentException("Expected 2 arguments on version but got " + args.size());
VersionPredicate predicate;
try {
predicate = VersionPredicate.parse(args.get(1).asString().getValue());
} catch (VersionParsingException e) {
throw new RuntimeException("Could not parse version predicate", e);
}
return DFinal.of(FabricLoader.getInstance().getModContainer(args.get(0).asString().getValue())
.map(c -> predicate.test(c.getMetadata().getVersion()))
.orElse(false));
}));
parameter.set("date", new DCallableObject(Map.of(
"today", new DDate(LocalDate::now)
), DFinal.of(args -> {
// Constructor
if (args.size() == 1) return new DDate(() -> LocalDate.ofEpochDay(args.get(0).asNumber().getValue().longValue()));
if (args.size() != 3) throw new IllegalArgumentException("Expected 3 arguments for full date constructor");
int a0 = args.get(0).asNumber().getValue().intValue();
int a1 = args.get(1).asNumber().getValue().intValue();
int a2 = args.get(2).asNumber().getValue().intValue();
return new DDate(() -> LocalDate.of(a0, a1, a2));
})));
parameter.set("time", new DCallableObject(Map.of(
"now", new DTime(LocalTime::now)
), DFinal.of(args -> {
// Constructor
if (args.size() == 1) return new DTime(() -> LocalTime.ofSecondOfDay(args.get(0).asNumber().getValue().intValue()));
if (args.size() != 3) throw new IllegalArgumentException("Expected 3 arguments for full time constructor");
int a0 = args.get(0).asNumber().getValue().intValue();
int a1 = args.get(1).asNumber().getValue().intValue();
int a2 = args.get(2).asNumber().getValue().intValue();
return new DTime(() -> LocalTime.of(a0, a1, a2));
})));
return parameter;
}
private record DCallableObject(Map<String, Dynamic<?>> value, DCallable callable) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return value;
}
@Override
public DCallable asCallable() {
return callable;
}
}
private record DDate(Supplier<LocalDate> date) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"year", DFinal.of(date.get().getYear()),
"month", DFinal.of(date.get().getMonthValue()),
"day", DFinal.of(date.get().getDayOfMonth())
);
}
@Override
public DString asString() {
return DFinal.of(date.get().toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(date.get().toEpochDay());
}
}
private record DTime(Supplier<LocalTime> time) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"hour", DFinal.of(time.get().getHour()),
"minute", DFinal.of(time.get().getMinute()),
"second", DFinal.of(time.get().getSecond())
);
}
@Override
public DString asString() {
return DFinal.of(time.get().toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(time.get().toSecondOfDay());
}
}
}

View File

@ -104,6 +104,6 @@ class ConditionJsonSerializationTest {
private boolean evaluateCondition(String json) {
BoolExpr condition = AttachmentHolder.attach(7, () -> GSON.fromJson(json, BoolExpr.class));
return condition.get(MetaCache.hydrateParameter(new ExpressionParameter()));
return condition.get(MuUtils.addDefault(new ExpressionParameter()));
}
}

View File

@ -0,0 +1,36 @@
package io.gitlab.jfronny.respackopts;
import io.gitlab.jfronny.muscript.*;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.respackopts.util.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TemporalExpressionTest {
@Test
void componentTest() {
ExpressionParameter param = MuUtils.addDefault(new ExpressionParameter());
assertEquals(10, Parser.parse("date(2020, 5, 10).day").asNumberExpr().get(param));
assertEquals(5, Parser.parse("date(2020, 5, 10).month").asNumberExpr().get(param));
assertEquals(2020, Parser.parse("date(2020, 5, 10).year").asNumberExpr().get(param));
assertEquals(59, Parser.parse("time(13, 45, 59).second").asNumberExpr().get(param));
assertEquals(45, Parser.parse("time(13, 45, 59).minute").asNumberExpr().get(param));
assertEquals(13, Parser.parse("time(13, 45, 59).hour").asNumberExpr().get(param));
}
@Test
void compareTest() {
ExpressionParameter param = MuUtils.addDefault(new ExpressionParameter());
assertTrue(Parser.parse("date(2020, 5, 10) > date(2020, 5, 9)").asBoolExpr().get(param));
assertTrue(Parser.parse("date(2020, 5, 10) > date(2020, 4, 10)").asBoolExpr().get(param));
assertTrue(Parser.parse("date(2020, 5, 10) > date(2019, 5, 10)").asBoolExpr().get(param));
assertTrue(Parser.parse("time(13, 45, 59) > time(13, 45, 58)").asBoolExpr().get(param));
assertTrue(Parser.parse("time(13, 45, 59) > time(13, 44, 59)").asBoolExpr().get(param));
assertTrue(Parser.parse("time(13, 45, 59) > time(12, 45, 59)").asBoolExpr().get(param));
}
}