Merge pull request #643 from altrisi/json-reading

Reading/Writing JSON in Scarpet
This commit is contained in:
gnembon authored and GitHub committed 2020-12-28 22:48:55 -04:00
commit 4aaec6562b
7 files changed
+175 -36

No files matched your search

+6 -3
View File
@@ -305,12 +305,15 @@ for folder listing.
Supported values for resource `type` are: Supported values for resource `type` are:
* `nbt` - NBT tag * `nbt` - NBT tag
* `json` - JSON file
* `text` - text resource with automatic newlines added * `text` - text resource with automatic newlines added
* `raw` - text resource without implied newlines * `raw` - text resource without implied newlines
* `folder` - for `list_files` only - indicting folder listing instead of files * `folder` - for `list_files` only - indicating folder listing instead of files
* `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above * `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder`, `shared_json` - shared versions of the above
NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. Text files have `.txt` extension, NBT files have extension `.nbt`, store one NBT tag, and return a NBT type value. JSON files have `.json` extension, store
Scarpet numbers, strings, lists, maps and `null` values. Anything else will be saved as a string (including NBT).
Text files have `.txt` extension,
stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be stores multiple lines of text and returns lists of all lines from the file. With `write_file`, multiple lines can be
sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each sent to the file at once. The only difference between `raw` and `text` types are automatic newlines added after each
record to the file. Since files are closed after each write, sending multiple lines of data to record to the file. Since files are closed after each write, sending multiple lines of data to
@@ -18,6 +18,8 @@ import carpet.script.value.NumericValue;
import carpet.script.value.StringValue; import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.google.gson.JsonElement;
import com.mojang.brigadier.builder.ArgumentBuilder; import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
@@ -705,7 +707,7 @@ public class CarpetScriptHost extends ScriptHost
public boolean removeResourceFile(String resource, boolean isShared, String type) public boolean removeResourceFile(String resource, boolean isShared, String type)
{ {
if (getName() == null && !isShared) return false; // if (getName() == null && !isShared) return false; //
return Module.dropExistingFile(main, resource, type.equals("nbt")?"nbt":"txt", isShared); return Module.dropExistingFile(main, resource, type, isShared);
} }
public boolean appendLogFile(String resource, boolean isShared, String type, List<String> data) public boolean appendLogFile(String resource, boolean isShared, String type, List<String> data)
@@ -714,16 +716,22 @@ public class CarpetScriptHost extends ScriptHost
return Module.appendToTextFile(main, resource, type, isShared, data); return Module.appendToTextFile(main, resource, type, isShared, data);
} }
public List<String> readTextResource(String resource, boolean isShared) public List<String> readTextResource(String resource, String type, boolean isShared)
{ {
if (getName() == null && !isShared) return null; // if (getName() == null && !isShared) return null;
return Module.listFile(main, resource, "txt", isShared); return Module.listFile(main, resource, type, isShared);
}
public JsonElement readJsonFile(String resource, String type, boolean isShared)
{
if (getName() == null && !isShared) return null;
return Module.readJsonFile(main, resource, type, isShared);
} }
public Stream<String> listFolder(String resource, String ext, boolean isShared) public Stream<String> listFolder(String resource, String type, boolean isShared)
{ {
if (getName() == null && !isShared) return null; // if (getName() == null && !isShared) return null; //
return Module.listFolder(main, resource, ext, isShared); return Module.listFolder(main, resource, type, isShared);
} }
+26 -11
View File
@@ -6,6 +6,7 @@ import carpet.fakes.MinecraftServerInterface;
import carpet.fakes.StatTypeInterface; import carpet.fakes.StatTypeInterface;
import carpet.fakes.ThreadedAnvilChunkStorageInterface; import carpet.fakes.ThreadedAnvilChunkStorageInterface;
import carpet.helpers.FeatureGenerator; import carpet.helpers.FeatureGenerator;
import carpet.script.bundled.Module;
import carpet.script.CarpetContext; import carpet.script.CarpetContext;
import carpet.script.CarpetEventServer; import carpet.script.CarpetEventServer;
import carpet.script.CarpetScriptHost; import carpet.script.CarpetScriptHost;
@@ -19,6 +20,7 @@ import carpet.script.argument.Vector3Argument;
import carpet.script.exception.ExitStatement; import carpet.script.exception.ExitStatement;
import carpet.script.exception.InternalExpressionException; import carpet.script.exception.InternalExpressionException;
import carpet.script.utils.FixedCommandSource; import carpet.script.utils.FixedCommandSource;
import carpet.script.utils.ScarpetJsonDeserializer;
import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.ShapeDispatcher;
import carpet.script.utils.WorldTools; import carpet.script.utils.WorldTools;
import carpet.script.value.EntityValue; import carpet.script.value.EntityValue;
@@ -33,7 +35,6 @@ import carpet.script.value.StringValue;
import carpet.script.value.Value; import carpet.script.value.Value;
import carpet.script.value.ValueConversions; import carpet.script.value.ValueConversions;
import carpet.utils.Messenger; import carpet.utils.Messenger;
import com.google.common.collect.ImmutableMap;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
@@ -69,6 +70,10 @@ import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple; import org.apache.commons.lang3.tuple.Triple;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -86,12 +91,7 @@ import static java.lang.Math.min;
public class Auxiliary { public class Auxiliary {
public static final String MARKER_STRING = "__scarpet_marker"; public static final String MARKER_STRING = "__scarpet_marker";
private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k)); private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
private static final Map<String, String> supportedTypes = ImmutableMap.of( public static final Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
"raw", ".txt",
"text", ".txt",
"nbt", ".nbt",
"folder", "folder"
);
public static String recognizeResource(Value value, boolean isFloder) public static String recognizeResource(Value value, boolean isFloder)
{ {
@@ -112,7 +112,7 @@ public class Auxiliary {
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_"); boolean shared = origtype.startsWith("shared_");
String type = shared ? origtype.substring(7) : origtype; //len(shared_) String type = shared ? origtype.substring(7) : origtype; //len(shared_)
if (!supportedTypes.containsKey(type)) if (!Module.supportedTypes.containsKey(type))
throw new InternalExpressionException("Unsupported file type: "+origtype); throw new InternalExpressionException("Unsupported file type: "+origtype);
if (type.equals("folder") && !isFloder) if (type.equals("folder") && !isFloder)
throw new InternalExpressionException("Folder types are no supported for this IO function"); throw new InternalExpressionException("Folder types are no supported for this IO function");
@@ -526,7 +526,7 @@ public class Auxiliary {
Text title = null; Text title = null;
if (lv.size() > 2) if (lv.size() > 2)
{ {
pVal = lv.get(2).evalValue(c); pVal = lv.get(2).evalValue(c);
if (pVal instanceof FormattedTextValue) if (pVal instanceof FormattedTextValue)
title = ((FormattedTextValue) pVal).getText(); title = ((FormattedTextValue) pVal).getText();
else else
@@ -803,7 +803,7 @@ public class Auxiliary {
expression.addLazyFunction("list_files", 2, (c, t, lv) -> expression.addLazyFunction("list_files", 2, (c, t, lv) ->
{ {
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight()); Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
if (files == null) return LazyValue.NULL; if (files == null) return LazyValue.NULL;
Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList())); Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
return (cc, tt) -> ret; return (cc, tt) -> ret;
@@ -820,9 +820,18 @@ public class Auxiliary {
if (state == null) return LazyValue.NULL; if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state); retVal = new NBTSerializableValue(state);
} }
else if (fdesc.getMiddle().equals("json"))
{
JsonElement json = ((CarpetScriptHost) c.host).readJsonFile(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
Value parsedJson = gson.fromJson(json, Value.class);
if (parsedJson == null)
retVal = Value.NULL;
else
retVal = parsedJson;
}
else else
{ {
List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getRight()); List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), fdesc.getMiddle(), fdesc.getRight());
if (content == null) return LazyValue.NULL; if (content == null) return LazyValue.NULL;
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList())); retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
} }
@@ -851,6 +860,12 @@ public class Auxiliary {
Tag tag = tagValue.getTag(); Tag tag = tagValue.getTag();
success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight()); success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
} }
else if (fdesc.getMiddle().equals("json"))
{
List<String> data = Collections.singletonList(gson.toJson(lv.get(2).evalValue(c).toJson()));
((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle());
success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
}
else else
{ {
List<String> data = new ArrayList<>(); List<String> data = new ArrayList<>();
@@ -25,6 +25,10 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
public class FileModule extends Module public class FileModule extends Module
{ {
private String name; private String name;
@@ -137,11 +141,11 @@ public class FileModule extends Module
return true; return true;
} }
public static boolean appendText(File filePath, boolean addNewLines, List<String> data) public static boolean appendText(Path filePath, boolean addNewLines, List<String> data)
{ {
try try
{ {
OutputStream out = Files.newOutputStream(filePath.toPath(), StandardOpenOption.APPEND, StandardOpenOption.CREATE); OutputStream out = Files.newOutputStream(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)))
{ {
for (String line: data) for (String line: data)
@@ -159,9 +163,9 @@ public class FileModule extends Module
} }
public static List<String> listFileContent(File filePath) public static List<String> listFileContent(Path filePath)
{ {
try (BufferedReader reader = Files.newBufferedReader(filePath.toPath(), StandardCharsets.UTF_8)) { try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) {
List<String> result = new ArrayList<>(); List<String> result = new ArrayList<>();
for (;;) { for (;;) {
String line = reader.readLine(); String line = reader.readLine();
@@ -176,6 +180,18 @@ public class FileModule extends Module
return null; return null;
} }
} }
public static JsonElement readJsonContent(Path filePath)
{
try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8))
{
return new JsonParser().parse(new JsonReader(reader));
}
catch (IOException e)
{
return null;
}
}
public static Stream<Path> listFiles(Path dir, String ext) public static Stream<Path> listFiles(Path dir, String ext)
{ {
+30 -11
View File
@@ -5,9 +5,13 @@ import net.minecraft.nbt.Tag;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class Module public abstract class Module
@@ -16,6 +20,13 @@ public abstract class Module
public abstract String getCode(); public abstract String getCode();
public abstract boolean isLibrary(); public abstract boolean isLibrary();
public final static Object writeIOSync = new Object(); public final static Object writeIOSync = new Object();
public final static Map<String, String> supportedTypes = ImmutableMap.of(
"raw", ".txt",
"text", ".txt",
"nbt", ".nbt",
"json", ".json",
"folder", "folder"
);
private static String getDescriptor(Module module, String file, boolean isShared) private static String getDescriptor(Module module, String file, boolean isShared)
{ {
@@ -32,7 +43,7 @@ public abstract class Module
public static Tag getData(Module module, String file, boolean isShared) public static Tag getData(Module module, String file, boolean isShared)
{ {
Path dataFile = resolveResource(module, file, "nbt", isShared); Path dataFile = resolveResource(module, file, ".nbt", isShared);
if (dataFile == null) return null; if (dataFile == null) return null;
if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null; if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); } synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
@@ -40,7 +51,7 @@ public abstract class Module
public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
{ {
Path dataFile = resolveResource(module, file, "nbt", isShared); Path dataFile = resolveResource(module, file, ".nbt", isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); } synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
@@ -48,30 +59,38 @@ public abstract class Module
public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message) public static boolean appendToTextFile(Module module, String resourceName, String type, boolean isShared, List<String> message)
{ {
Path dataFile = resolveResource(module, resourceName, "txt", isShared); Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), type.equals("text"), message); } synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); }
} }
public static boolean dropExistingFile(Module module, String resourceName, String ext, boolean isShared) public static boolean dropExistingFile(Module module, String resourceName, String type, boolean isShared)
{ {
Path dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
synchronized (writeIOSync) { return dataFile.toFile().delete(); } synchronized (writeIOSync) { return dataFile.toFile().delete(); }
} }
public static List<String> listFile(Module module, String resourceName, String ext, boolean isShared) public static List<String> listFile(Module module, String resourceName, String type, boolean isShared)
{ {
Path dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return null; if (dataFile == null) return null;
if (!dataFile.toFile().exists()) return null; if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile.toFile()); } synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); }
}
public static JsonElement readJsonFile(Module module, String resourceName, String type, boolean isShared) {
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
if (dataFile == null) return null;
if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.readJsonContent(dataFile); }
} }
public static Stream<String> listFolder(Module module, String resourceName, String ext, boolean isShared) public static Stream<String> listFolder(Module module, String resourceName, String type, boolean isShared)
{ {
Path dir = resolveResource(module, resourceName, null, isShared); Path dir = resolveResource(module, resourceName, null, isShared);
String ext = supportedTypes.get(type);
if (dir == null) return null; if (dir == null) return null;
if (!Files.exists(dir)) return null; if (!Files.exists(dir)) return null;
Stream<Path> result; Stream<Path> result;
@@ -87,7 +106,7 @@ public abstract class Module
private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared) private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
{ {
if (!isShared && (module == null || module.getName() == null)) return null; if (!isShared && (module == null || module.getName() == null)) return null;
return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":"."+ext)); return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":ext));
} }
@Override @Override
@@ -0,0 +1,78 @@
package carpet.script.utils;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import carpet.script.value.ListValue;
import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
public class ScarpetJsonDeserializer implements JsonDeserializer<Value>{
@Override
public Value deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return parseElement(json);
}
private Value parseElement(JsonElement element) throws JsonParseException {
if (element.isJsonObject())
{
return parseMap(element.getAsJsonObject());
}
else if (element.isJsonArray())
{
return parseList(element.getAsJsonArray());
}
else if (element.isJsonPrimitive())
{
return parsePrimitive(element.getAsJsonPrimitive());
}
return Value.NULL;
}
private Value parseMap(JsonObject jsonMap) throws JsonParseException {
Map<Value, Value> map = new HashMap<Value, Value>();
jsonMap.entrySet().forEach(entry -> {
map.put(new StringValue(entry.getKey()), parseElement(entry.getValue()));
});
return MapValue.wrap(map);
}
private Value parseList(JsonArray jsonList) throws JsonParseException {
List<Value> list = new ArrayList<Value>();
jsonList.forEach(elem -> {
list.add(parseElement(elem));
});
return new ListValue(list);
}
private Value parsePrimitive(JsonPrimitive primitive) throws JsonParseException {
if (primitive.isString())
{
return new StringValue(primitive.getAsString());
}
else if (primitive.isBoolean())
{
return primitive.getAsBoolean() ? Value.TRUE : Value.FALSE;
}
else if (primitive.isNumber())
{
return NumericValue.of(primitive.getAsNumber());
}
return Value.NULL;
}
}
@@ -4,7 +4,7 @@ import carpet.script.exception.InternalExpressionException;
import java.util.Iterator; import java.util.Iterator;
public abstract class AbstractListValue extends Value public abstract class AbstractListValue extends Value implements Iterable<Value>
{ {
public abstract Iterator<Value> iterator(); public abstract Iterator<Value> iterator();
public void fatality() { } public void fatality() { }