added list_files, fixes #589

This commit is contained in:
gnembon committed 2020-12-03 04:05:11 -04:00
1 parent 5a15c115ac
commit 6e5af5bfc8
8 files changed
+126 -74

No files matched your search

+14 -19
View File
@@ -275,13 +275,15 @@ Available output types:
### `read_file(resource, type)` ### `read_file(resource, type)`
### `delete_file(resource, type)` ### `delete_file(resource, type)`
### `write_file(resource, type, data, ...)` ### `write_file(resource, type, data, ...)`
### `list_files(resource, type)`
With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its With the specified `resource` in the scripts folder, of a specific `type`, writes/appends `data` to it, reads its
content, or deletes the resource. content, deletes the resource, or lists other files under this resource.
Resource is identified by a path to the file. Resource is identified by a path to the file.
A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped A path can contain letters, numbers, characters `-`, `+`, or `_`, and a folder separator: `'/'`. Any other characters are stripped
from the name. Empty descriptors are invalid. Do not add file extensions to the descriptor - extensions are inferred from the name. Empty descriptors are invalid, except for `list_files` where it means the root folder.
Do not add file extensions to the descriptor - extensions are inferred
based on the `type` of the file. based on the `type` of the file.
Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific Resources can be located in the app specific space, or a shared space for all the apps. Accessing of app-specific
@@ -296,33 +298,26 @@ specific data directory is under `world/scripts/foo.data/...`, and shared data s
The default no-name app, via `/script run` command can only save/load/read files from the shared space. The default no-name app, via `/script run` command can only save/load/read files from the shared space.
Functions return `null` if an error is encounter or no file is present (for read and delete operations). Returns `true` Functions return `null` if an error is encounter or no file is present (for read, list and delete operations). Returns `true`
for success writes and deletes, and requested data, based on the file type, for read operations. for success writes and deletes, and requested data, based on the file type, for read operations. It returns list of files
for folder listing.
NBT files can be written once as they an store one tag at a time. Consecutive writes will overwrite previous data.
Write operations to text files always result in appending to the existing file, so consecutive writes will increase
the size of the file and add data to it. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
* `write_file('temp', 'text', 'foo', 'bar', 'baz')` or
* write_file('temp', 'text', l('foo', 'bar', 'baz'))
To log a single line of string Supported values for resource `type` are:
Supported values for resource `type` is:
* `nbt` - NBT tag * `nbt` - NBT tag
* `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
* `shared_nbt`, `shared_text`, `shared_raw` - shared versions of the above * `folder` - for `list_files` only - indicting folder listing instead of files
* `shared_nbt`, `shared_text`, `shared_raw`, `shared_folder` - 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. 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. record to the file. Since files are closed after each write, sending multiple lines of data to
write is beneficial for writing speed. To send multiple packs of data, either provide them flat or as a list in the
third argument.
<pre> <pre>
write_file('foo', 'shared_text, l('one', 'two')); write_file('foo', 'shared_text, ['one', 'two']);
write_file('foo', 'shared_text', 'three\n', 'four\n'); write_file('foo', 'shared_text', 'three\n', 'four\n');
write_file('foo', 'shared_raw', 'five\n', 'six\n'); write_file('foo', 'shared_raw', 'five\n', 'six\n');
@@ -55,6 +55,7 @@ item_category
last_tick_times last_tick_times
light light
liquid liquid
list_files
load_app_data load_app_data
loaded loaded
loaded_status loaded_status
@@ -82,7 +82,7 @@
plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers plop harvest destroy schedule nbt draw_shape create_marker remove_all_markers
load_app_data store_app_data read_file write_file delete_file load_app_data store_app_data read_file write_file delete_file list_files
escape_nbt parse_nbt encode_nbt tag_matches statistic escape_nbt parse_nbt encode_nbt tag_matches statistic
</Keywords> </Keywords>
<Keywords name="Keywords3">euler pi null true false</Keywords> <Keywords name="Keywords3">euler pi null true false</Keywords>
@@ -41,6 +41,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.lang.Math.max; import static java.lang.Math.max;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.server.command.CommandManager.literal;
@@ -673,6 +674,12 @@ public class CarpetScriptHost extends ScriptHost
return Module.listFile(main, resource, "txt", isShared); return Module.listFile(main, resource, "txt", isShared);
} }
public Stream<String> listFolder(String resource, String ext, boolean isShared)
{
if (getName() == null && !isShared) return null; //
return Module.listFolder(main, resource, ext, isShared);
}
public void tick() public void tick()
{ {
@@ -137,7 +137,7 @@ public class CarpetScriptServer
Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts"); Path folder = server.getSavePath(WorldSavePath.ROOT).resolve("scripts");
Files.createDirectories(folder); Files.createDirectories(folder);
Optional<Path> Optional<Path>
scriptPath = Files.walk(folder) scriptPath = Files.list(folder)
.filter(script -> .filter(script ->
script.getFileName().toString().equalsIgnoreCase(name+".sc") || script.getFileName().toString().equalsIgnoreCase(name+".sc") ||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl")) (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name+".scl"))
@@ -149,7 +149,7 @@ public class CarpetScriptServer
{ {
Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts"); Path globalFolder = FabricLoader.getInstance().getConfigDir().resolve("carpet/scripts");
Files.createDirectories(globalFolder); Files.createDirectories(globalFolder);
scriptPath = Files.walk(globalFolder) scriptPath = Files.list(globalFolder)
.filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") || .filter(script -> script.getFileName().toString().equalsIgnoreCase(name + ".sc") ||
(allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl"))) (allowLibraries && script.getFileName().toString().equalsIgnoreCase(name + ".scl")))
.findFirst(); .findFirst();
+48 -28
View File
@@ -32,6 +32,7 @@ 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.command.argument.EntityAnchorArgumentType; import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -50,7 +51,6 @@ import net.minecraft.particle.ParticleEffect;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.CommandOutput; import net.minecraft.server.command.CommandOutput;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
@@ -69,6 +69,7 @@ import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.Registry; import net.minecraft.util.registry.Registry;
import net.minecraft.world.World; 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 java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -87,6 +88,12 @@ 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(
"raw", ".txt",
"text", ".txt",
"nbt", ".nbt",
"folder", "folder"
);
public static String recognizeResource(Value value, boolean isFloder) public static String recognizeResource(Value value, boolean isFloder)
{ {
@@ -100,6 +107,20 @@ public class Auxiliary {
return file; return file;
} }
public static Triple<String, String, Boolean> getFileDescriptor(List<LazyValue> lv, Context c, boolean isFloder)
{
if (lv.size() < 2) throw new InternalExpressionException("File functions require path and type as first two arguments");
String resource = recognizeResource(lv.get(0).evalValue(c), isFloder);
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))
throw new InternalExpressionException("Unsupported file type: "+origtype);
if (type.equals("folder") && !isFloder)
throw new InternalExpressionException("Folder types are no supported for this IO function");
return Triple.of(resource, type, shared);
}
public static void apply(Expression expression) public static void apply(Expression expression)
{ {
expression.addLazyFunction("sound", -1, (c, t, lv) -> { expression.addLazyFunction("sound", -1, (c, t, lv) -> {
@@ -785,23 +806,29 @@ public class Auxiliary {
return (_c, _t) -> res; // pass through for variables return (_c, _t) -> res; // pass through for variables
}); });
expression.addLazyFunction("read_file", 2, (c, t, lv) -> { expression.addLazyFunction("list_files", 2, (c, t, lv) ->
String resource = recognizeResource(lv.get(0).evalValue(c), false); {
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, true);
boolean shared = origtype.startsWith("shared_"); Stream<String> files = ((CarpetScriptHost) c.host).listFolder(fdesc.getLeft(), supportedTypes.get(fdesc.getMiddle()), fdesc.getRight());
String type = shared ? origtype.substring(7) : origtype; //len(shared_) if (files == null) return LazyValue.NULL;
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt")) Value ret = ListValue.wrap(files.map(StringValue::of).collect(Collectors.toList()));
throw new InternalExpressionException("Unsupported file type: "+origtype); return (cc, tt) -> ret;
});
expression.addLazyFunction("read_file", 2, (c, t, lv) ->
{
Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
Value retVal; Value retVal;
if (type.equals("nbt")) if (fdesc.getMiddle().equals("nbt"))
{ {
Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared); Tag state = ((CarpetScriptHost) c.host).readFileTag(fdesc.getLeft(), fdesc.getRight());
if (state == null) return LazyValue.NULL; if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state); retVal = new NBTSerializableValue(state);
} }
else else
{ {
List<String> content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared); List<String> content = ((CarpetScriptHost) c.host).readTextResource(fdesc.getLeft(), 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()));
} }
@@ -809,33 +836,26 @@ public class Auxiliary {
}); });
expression.addLazyFunction("delete_file", 2, (c, t, lv) -> { expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
String resource = recognizeResource(lv.get(0).evalValue(c), false); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT); return ((CarpetScriptHost) c.host).removeResourceFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle())
boolean shared = origtype.startsWith("shared_"); ? LazyValue.TRUE
String type = shared ? origtype.substring(7) : origtype; //len(shared_) : LazyValue.FALSE;
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type);
return success?LazyValue.TRUE:LazyValue.FALSE;
}); });
expression.addLazyFunction("write_file", -1, (c, t, lv) -> { expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments"); if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
String resource = recognizeResource(lv.get(0).evalValue(c), false); Triple<String, String, Boolean> fdesc = getFileDescriptor(lv, c, false);
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 (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success; boolean success;
if (type.equals("nbt")) if (fdesc.getMiddle().equals("nbt"))
{ {
Value val = lv.get(2).evalValue(c); Value val = lv.get(2).evalValue(c);
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue) NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val ? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString()); : new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag(); Tag tag = tagValue.getTag();
success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared); success = ((CarpetScriptHost) c.host).writeTagFile(tag, fdesc.getLeft(), fdesc.getRight());
} }
else else
{ {
@@ -860,7 +880,7 @@ public class Auxiliary {
data.add(lv.get(i).evalValue(c).getString()); data.add(lv.get(i).evalValue(c).getString());
} }
} }
success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data); success = ((CarpetScriptHost) c.host).appendLogFile(fdesc.getLeft(), fdesc.getRight(), fdesc.getMiddle(), data);
} }
return success?LazyValue.TRUE:LazyValue.FALSE; return success?LazyValue.TRUE:LazyValue.FALSE;
}); });
@@ -23,6 +23,7 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.stream.Stream;
public class FileModule extends Module public class FileModule extends Module
{ {
@@ -175,4 +176,18 @@ public class FileModule extends Module
return null; return null;
} }
} }
public static Stream<Path> listFiles(Path dir, String ext)
{
boolean folderListing = ext.equals("folder");
try
{
return Files.list(dir).
filter(path -> folderListing?Files.isDirectory(path):path.toString().endsWith(ext));
}
catch (IOException ignored)
{
return null;
}
}
} }
+38 -24
View File
@@ -3,10 +3,12 @@ package carpet.script.bundled;
import carpet.CarpetServer; import carpet.CarpetServer;
import net.minecraft.nbt.Tag; import net.minecraft.nbt.Tag;
import net.minecraft.util.WorldSavePath; import net.minecraft.util.WorldSavePath;
import org.apache.commons.io.FilenameUtils;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
public abstract class Module public abstract class Module
{ {
@@ -19,61 +21,73 @@ public abstract class Module
{ {
if (isShared) if (isShared)
{ {
return "shared/"+file; return file.isEmpty()?"shared":"shared/"+file;
} }
else if (module != null && module.getName() != null) // appdata if (module != null && module.getName() != null) // appdata
{ {
return module.getName()+".data"+(file==null?"":"/"+file); return module.getName()+".data"+(file==null || file.isEmpty()?"":"/"+file);
}
else
{
throw new RuntimeException("Invalid file descriptor: "+file);
} }
throw new RuntimeException("Invalid file descriptor: "+file);
} }
public static Tag getData(Module module, String file, boolean isShared) public static Tag getData(Module module, String file, boolean isShared)
{ {
File 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.toPath()) || !(dataFile.isFile())) return null; if (!Files.exists(dataFile) || !(dataFile.toFile().isFile())) return null;
synchronized (writeIOSync) { return FileModule.read(dataFile); } synchronized (writeIOSync) { return FileModule.read(dataFile.toFile()); }
} }
public static boolean saveData(Module module, String file, Tag globalState, boolean isShared) public static boolean saveData(Module module, String file, Tag globalState, boolean isShared)
{ {
File 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.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.write(globalState, dataFile); } synchronized (writeIOSync) { return FileModule.write(globalState, dataFile.toFile()); }
} }
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)
{ {
File dataFile = resolveResource(module, resourceName, "txt", isShared); Path dataFile = resolveResource(module, resourceName, "txt", isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
if (!Files.exists(dataFile.toPath().getParent()) && !dataFile.getParentFile().mkdirs()) return false; if (!Files.exists(dataFile.getParent()) && !dataFile.toFile().getParentFile().mkdirs()) return false;
synchronized (writeIOSync) { return FileModule.appendText(dataFile, type.equals("text"), message); } synchronized (writeIOSync) { return FileModule.appendText(dataFile.toFile(), 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 ext, boolean isShared)
{ {
File dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return false; if (dataFile == null) return false;
synchronized (writeIOSync) { return dataFile.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 ext, boolean isShared)
{ {
File dataFile = resolveResource(module, resourceName, ext, isShared); Path dataFile = resolveResource(module, resourceName, ext, isShared);
if (dataFile == null) return null; if (dataFile == null) return null;
if (!dataFile.exists()) return null; if (!dataFile.toFile().exists()) return null;
synchronized (writeIOSync) { return FileModule.listFileContent(dataFile); } synchronized (writeIOSync) { return FileModule.listFileContent(dataFile.toFile()); }
} }
private static File resolveResource(Module module, String resourceName, String ext, boolean isShared) public static Stream<String> listFolder(Module module, String resourceName, String ext, boolean isShared)
{
Path dir = resolveResource(module, resourceName, null, isShared);
if (dir == null) return null;
if (!Files.exists(dir)) return null;
Stream<Path> result;
synchronized (writeIOSync) { result = FileModule.listFiles(dir, ext); }
if (result == null) return null;
Path rootPath = resolveResource(module, "", null, isShared);
Stream<String> strings = result.map(p -> rootPath.relativize(p).toString().replaceAll("[\\\\/]","/"));
if (!ext.equals("folder"))
return strings.map(FilenameUtils::removeExtension);
return strings;
}
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).toFile(); return CarpetServer.minecraft_server.getSavePath(WorldSavePath.ROOT).resolve("scripts/"+getDescriptor(module, resourceName, isShared)+(ext==null?"":"."+ext));
} }
@Override @Override