mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-13 02:40:28 +00:00
Port fs.find to CraftOS
Also add support for "?" style wildcards. Closes #1455.
This commit is contained in:
parent
77ac04cb7a
commit
c7f3d4f45d
@ -443,28 +443,6 @@ public class FSAPI implements ILuaAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for files matching a string with wildcards.
|
|
||||||
* <p>
|
|
||||||
* This string is formatted like a normal path string, but can include any
|
|
||||||
* number of wildcards ({@code *}) to look for files matching anything.
|
|
||||||
* For example, <code>rom/*/command*</code> will look for any path starting with
|
|
||||||
* {@code command} inside any subdirectory of {@code /rom}.
|
|
||||||
*
|
|
||||||
* @param path The wildcard-qualified path to search for.
|
|
||||||
* @return A list of paths that match the search string.
|
|
||||||
* @throws LuaException If the path doesn't exist.
|
|
||||||
* @cc.since 1.6
|
|
||||||
*/
|
|
||||||
@LuaFunction
|
|
||||||
public final String[] find(String path) throws LuaException {
|
|
||||||
try (var ignored = environment.time(Metrics.FS_OPS)) {
|
|
||||||
return getFileSystem().find(path);
|
|
||||||
} catch (FileSystemException e) {
|
|
||||||
throw new LuaException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the capacity of the drive the path is located on.
|
* Returns the capacity of the drive the path is located on.
|
||||||
*
|
*
|
||||||
|
@ -166,47 +166,6 @@ public class FileSystem {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void findIn(String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
|
|
||||||
var list = list(dir);
|
|
||||||
for (var entry : list) {
|
|
||||||
var entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
|
|
||||||
if (wildPattern.matcher(entryPath).matches()) {
|
|
||||||
matches.add(entryPath);
|
|
||||||
}
|
|
||||||
if (isDir(entryPath)) {
|
|
||||||
findIn(entryPath, matches, wildPattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized String[] find(String wildPath) throws FileSystemException {
|
|
||||||
// Match all the files on the system
|
|
||||||
wildPath = sanitizePath(wildPath, true);
|
|
||||||
|
|
||||||
// If we don't have a wildcard at all just check the file exists
|
|
||||||
var starIndex = wildPath.indexOf('*');
|
|
||||||
if (starIndex == -1) {
|
|
||||||
return exists(wildPath) ? new String[]{ wildPath } : new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar
|
|
||||||
var prevDir = wildPath.substring(0, starIndex).lastIndexOf('/');
|
|
||||||
var startDir = prevDir == -1 ? "" : wildPath.substring(0, prevDir);
|
|
||||||
|
|
||||||
// If this isn't a directory then just abort
|
|
||||||
if (!isDir(startDir)) return new String[0];
|
|
||||||
|
|
||||||
// Scan as normal, starting from this directory
|
|
||||||
var wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
|
|
||||||
List<String> matches = new ArrayList<>();
|
|
||||||
findIn(startDir, matches, wildPattern);
|
|
||||||
|
|
||||||
// Return matches
|
|
||||||
var array = new String[matches.size()];
|
|
||||||
matches.toArray(array);
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean exists(String path) throws FileSystemException {
|
public synchronized boolean exists(String path) throws FileSystemException {
|
||||||
path = sanitizePath(path);
|
path = sanitizePath(path);
|
||||||
var mount = getMount(path);
|
var mount = getMount(path);
|
||||||
@ -400,21 +359,20 @@ public class FileSystem {
|
|||||||
|
|
||||||
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
|
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
|
||||||
|
|
||||||
|
// IMPORTANT: Both arrays are sorted by ASCII value.
|
||||||
|
private static final char[] specialChars = new char[]{ '"', '*', ':', '<', '>', '?', '|' };
|
||||||
|
private static final char[] specialCharsAllowWildcards = new char[]{ '"', ':', '<', '>', '|' };
|
||||||
|
|
||||||
public static String sanitizePath(String path, boolean allowWildcards) {
|
public static String sanitizePath(String path, boolean allowWildcards) {
|
||||||
// Allow windowsy slashes
|
// Allow windowsy slashes
|
||||||
path = path.replace('\\', '/');
|
path = path.replace('\\', '/');
|
||||||
|
|
||||||
// Clean the path or illegal characters.
|
// Clean the path or illegal characters.
|
||||||
final var specialChars = new char[]{
|
|
||||||
'"', ':', '<', '>', '?', '|', // Sorted by ascii value (important)
|
|
||||||
};
|
|
||||||
|
|
||||||
var cleanName = new StringBuilder();
|
var cleanName = new StringBuilder();
|
||||||
|
var allowedChars = allowWildcards ? specialCharsAllowWildcards : specialChars;
|
||||||
for (var i = 0; i < path.length(); i++) {
|
for (var i = 0; i < path.length(); i++) {
|
||||||
var c = path.charAt(i);
|
var c = path.charAt(i);
|
||||||
if (c >= 32 && Arrays.binarySearch(specialChars, c) < 0 && (allowWildcards || c != '*')) {
|
if (c >= 32 && Arrays.binarySearch(allowedChars, c) < 0) cleanName.append(c);
|
||||||
cleanName.append(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
path = cleanName.toString();
|
path = cleanName.toString();
|
||||||
|
|
||||||
|
@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
|||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function find_aux(path, parts, i, out)
|
||||||
|
local part = parts[i]
|
||||||
|
if not part then
|
||||||
|
-- If we're at the end of the pattern, ensure our path exists and append it.
|
||||||
|
if fs.exists(path) then out[#out + 1] = path end
|
||||||
|
elseif part.exact then
|
||||||
|
-- If we're an exact match, just recurse into this directory.
|
||||||
|
return find_aux(fs.combine(path, part.contents), parts, i + 1, out)
|
||||||
|
else
|
||||||
|
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
|
||||||
|
-- matching file.
|
||||||
|
if not fs.isDir(path) then return end
|
||||||
|
|
||||||
|
local files = fs.list(path)
|
||||||
|
for j = 1, #files do
|
||||||
|
local file = files[j]
|
||||||
|
if file:find(part.contents) then find_aux(fs.combine(path, file), parts, i + 1, out) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local find_escape = {
|
||||||
|
-- Escape standard Lua pattern characters
|
||||||
|
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
|
||||||
|
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
|
||||||
|
-- Aside from our wildcards.
|
||||||
|
["*"] = ".*",
|
||||||
|
["?"] = ".",
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[- Searches for files matching a string with wildcards.
|
||||||
|
|
||||||
|
This string looks like a normal path string, but can include wildcards, which
|
||||||
|
can match multiple paths:
|
||||||
|
|
||||||
|
- "?" matches any single character in a file name.
|
||||||
|
- "*" matches any number of characters.
|
||||||
|
|
||||||
|
For example, `rom/*/command*` will look for any path starting with `command`
|
||||||
|
inside any subdirectory of `/rom`.
|
||||||
|
|
||||||
|
Note that these wildcards match a single segment of the path. For instance
|
||||||
|
`rom/*.lua` will include `rom/startup.lua` but _not_ include `rom/programs/list.lua`.
|
||||||
|
|
||||||
|
@tparam string path The wildcard-qualified path to search for.
|
||||||
|
@treturn { string... } A list of paths that match the search string.
|
||||||
|
@throws If the supplied path was invalid.
|
||||||
|
@since 1.6
|
||||||
|
@changed 1.106.0 Added support for the `?` wildcard.
|
||||||
|
|
||||||
|
@usage List all Markdown files in the help folder
|
||||||
|
|
||||||
|
fs.find("rom/help/*.md")
|
||||||
|
]]
|
||||||
|
function fs.find(pattern)
|
||||||
|
expect(1, pattern, "string")
|
||||||
|
|
||||||
|
pattern = fs.combine(pattern) -- Normalise the path, removing ".."s.
|
||||||
|
|
||||||
|
-- If the pattern is trying to search outside the computer root, just abort.
|
||||||
|
-- This will fail later on anyway.
|
||||||
|
if pattern == ".." or pattern:sub(1, 3) == "../" then
|
||||||
|
error("/" .. pattern .. ": Invalid Path", 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If we've no wildcards, just check the file exists.
|
||||||
|
if not pattern:find("[*?]") then
|
||||||
|
if fs.exists(pattern) then return { pattern } else return {} end
|
||||||
|
end
|
||||||
|
|
||||||
|
local parts = {}
|
||||||
|
for part in pattern:gmatch("[^/]+") do
|
||||||
|
if part:find("[*?]") then
|
||||||
|
parts[#parts + 1] = {
|
||||||
|
exact = false,
|
||||||
|
contents = "^" .. part:gsub(".", find_escape) .. "$",
|
||||||
|
}
|
||||||
|
else
|
||||||
|
parts[#parts + 1] = { exact = true, contents = part }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
find_aux("", parts, 1, out)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
--- Returns true if a path is mounted to the parent filesystem.
|
--- Returns true if a path is mounted to the parent filesystem.
|
||||||
--
|
--
|
||||||
-- The root filesystem "/" is considered a mount, along with disk folders and
|
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||||
|
@ -87,6 +87,53 @@ describe("The fs library", function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe("fs.find", function()
|
||||||
|
it("fails on invalid paths", function()
|
||||||
|
expect.error(fs.find, ".."):eq("/..: Invalid Path")
|
||||||
|
expect.error(fs.find, "../foo/bar"):eq("/../foo/bar: Invalid Path")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns nothing on non-existent files", function()
|
||||||
|
expect(fs.find("no/such/file")):same {}
|
||||||
|
expect(fs.find("no/such/*")):same {}
|
||||||
|
expect(fs.find("no/*/file")):same {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns a single file", function()
|
||||||
|
expect(fs.find("rom")):same { "rom" }
|
||||||
|
expect(fs.find("rom/motd.txt")):same { "rom/motd.txt" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("supports the '*' wildcard", function()
|
||||||
|
expect(fs.find("rom/*")):same {
|
||||||
|
"rom/apis",
|
||||||
|
"rom/autorun",
|
||||||
|
"rom/help",
|
||||||
|
"rom/modules",
|
||||||
|
"rom/motd.txt",
|
||||||
|
"rom/programs",
|
||||||
|
"rom/startup.lua",
|
||||||
|
}
|
||||||
|
expect(fs.find("rom/*/command")):same {
|
||||||
|
"rom/apis/command",
|
||||||
|
"rom/modules/command",
|
||||||
|
"rom/programs/command",
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fs.find("rom/*/lua*")):same {
|
||||||
|
"rom/help/lua.txt",
|
||||||
|
"rom/programs/lua.lua",
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("supports the '?' wildcard", function()
|
||||||
|
expect(fs.find("rom/programs/mo??.lua")):same {
|
||||||
|
"rom/programs/motd.lua",
|
||||||
|
"rom/programs/move.lua",
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
describe("fs.combine", function()
|
describe("fs.combine", function()
|
||||||
it("removes . and ..", function()
|
it("removes . and ..", function()
|
||||||
expect(fs.combine("./a/b")):eq("a/b")
|
expect(fs.combine("./a/b")):eq("a/b")
|
||||||
|
Loading…
Reference in New Issue
Block a user