Merge pull request #643 from altrisi/json-reading
Reading/Writing JSON in Scarpet
This commit is contained in:
7 files changed
+175
-36
No files matched your search
@@ -305,12 +305,15 @@ for folder listing.
|
||||
|
||||
Supported values for resource `type` are:
|
||||
* `nbt` - NBT tag
|
||||
* `json` - JSON file
|
||||
* `text` - text resource with automatic newlines added
|
||||
* `raw` - text resource without implied newlines
|
||||
* `folder` - for `list_files` only - indicting folder listing instead of files
|
||||
* `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - shared versions of the above
|
||||
* `folder` - for `list_files` only - indicating folder listing instead of files
|
||||
* `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
|
||||
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
|
||||
|
||||
@@ -18,6 +18,8 @@ import carpet.script.value.NumericValue;
|
||||
import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.utils.Messenger;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
@@ -705,7 +707,7 @@ public class CarpetScriptHost extends ScriptHost
|
||||
public boolean removeResourceFile(String resource, boolean isShared, String type)
|
||||
{
|
||||
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)
|
||||
@@ -714,16 +716,22 @@ public class CarpetScriptHost extends ScriptHost
|
||||
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; //
|
||||
return Module.listFile(main, resource, "txt", isShared);
|
||||
if (getName() == null && !isShared) return null;
|
||||
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; //
|
||||
return Module.listFolder(main, resource, ext, isShared);
|
||||
return Module.listFolder(main, resource, type, isShared);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import carpet.fakes.MinecraftServerInterface;
|
||||
import carpet.fakes.StatTypeInterface;
|
||||
import carpet.fakes.ThreadedAnvilChunkStorageInterface;
|
||||
import carpet.helpers.FeatureGenerator;
|
||||
import carpet.script.bundled.Module;
|
||||
import carpet.script.CarpetContext;
|
||||
import carpet.script.CarpetEventServer;
|
||||
import carpet.script.CarpetScriptHost;
|
||||
@@ -19,6 +20,7 @@ import carpet.script.argument.Vector3Argument;
|
||||
import carpet.script.exception.ExitStatement;
|
||||
import carpet.script.exception.InternalExpressionException;
|
||||
import carpet.script.utils.FixedCommandSource;
|
||||
import carpet.script.utils.ScarpetJsonDeserializer;
|
||||
import carpet.script.utils.ShapeDispatcher;
|
||||
import carpet.script.utils.WorldTools;
|
||||
import carpet.script.value.EntityValue;
|
||||
@@ -33,7 +35,6 @@ import carpet.script.value.StringValue;
|
||||
import carpet.script.value.Value;
|
||||
import carpet.script.value.ValueConversions;
|
||||
import carpet.utils.Messenger;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
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.Triple;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -86,12 +91,7 @@ import static java.lang.Math.min;
|
||||
public class Auxiliary {
|
||||
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, String> supportedTypes = ImmutableMap.of(
|
||||
"raw", ".txt",
|
||||
"text", ".txt",
|
||||
"nbt", ".nbt",
|
||||
"folder", "folder"
|
||||
);
|
||||
public static final Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Value.class, new ScarpetJsonDeserializer()).create();
|
||||
|
||||
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);
|
||||
boolean shared = origtype.startsWith("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);
|
||||
if (type.equals("folder") && !isFloder)
|
||||
throw new InternalExpressionException("Folder types are no supported for this IO function");
|
||||
@@ -526,7 +526,7 @@ public class Auxiliary {
|
||||
Text title = null;
|
||||
if (lv.size() > 2)
|
||||
{
|
||||
pVal = lv.get(2).evalValue(c);
|
||||
pVal = lv.get(2).evalValue(c);
|
||||
if (pVal instanceof FormattedTextValue)
|
||||
title = ((FormattedTextValue) pVal).getText();
|
||||
else
|
||||
@@ -803,7 +803,7 @@ public class Auxiliary {
|
||||
expression.addLazyFunction("list_files", 2, (c, t, lv) ->
|
||||
{
|
||||
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;
|
||||
Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
|
||||
return (cc, tt) -> ret;
|
||||
@@ -820,9 +820,18 @@ public class Auxiliary {
|
||||
if (state == null) return LazyValue.NULL;
|
||||
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
|
||||
{
|
||||
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;
|
||||
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
|
||||
}
|
||||
@@ -851,6 +860,12 @@ public class Auxiliary {
|
||||
Tag tag = tagValue.getTag();
|
||||
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
|
||||
{
|
||||
List<String> data = new ArrayList<>();
|
||||
|
||||
@@ -25,6 +25,10 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
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
|
||||
{
|
||||
private String name;
|
||||
@@ -137,11 +141,11 @@ public class FileModule extends Module
|
||||
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
|
||||
{
|
||||
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)))
|
||||
{
|
||||
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<>();
|
||||
for (;;) {
|
||||
String line = reader.readLine();
|
||||
@@ -176,6 +180,18 @@ public class FileModule extends Module
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -5,9 +5,13 @@ import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
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.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class Module
|
||||
@@ -16,6 +20,13 @@ public abstract class Module
|
||||
public abstract String getCode();
|
||||
public abstract boolean isLibrary();
|
||||
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)
|
||||
{
|
||||
@@ -32,7 +43,7 @@ public abstract class Module
|
||||
|
||||
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 (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
|
||||
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)
|
||||
{
|
||||
Path dataFile = resolveResource(module, file, "nbt", isShared);
|
||||
Path dataFile = resolveResource(module, file, ".nbt", isShared);
|
||||
if (dataFile == null) return false;
|
||||
if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
|
||||
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)
|
||||
{
|
||||
Path dataFile = resolveResource(module, resourceName, "txt", isShared);
|
||||
Path dataFile = resolveResource(module, resourceName, supportedTypes.get(type), isShared);
|
||||
if (dataFile == null) 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;
|
||||
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.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);
|
||||
String ext = supportedTypes.get(type);
|
||||
if (dir == null) return null;
|
||||
if (!Files.exists(dir)) return null;
|
||||
Stream<Path> result;
|
||||
@@ -87,7 +106,7 @@ public abstract class Module
|
||||
private static Path resolveResource(Module module, String resourceName, String ext, boolean isShared)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
public abstract class AbstractListValue extends Value
|
||||
public abstract class AbstractListValue extends Value implements Iterable<Value>
|
||||
{
|
||||
public abstract Iterator<Value> iterator();
|
||||
public void fatality() { }
|
||||
|
||||
Reference in new issue
Block a user