mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-11-05 01:26:20 +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.
|
||||
*
|
||||
|
@ -166,47 +166,6 @@ public class FileSystem {
|
||||
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 {
|
||||
path = sanitizePath(path);
|
||||
var mount = getMount(path);
|
||||
@ -400,21 +359,20 @@ public class FileSystem {
|
||||
|
||||
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) {
|
||||
// Allow windowsy slashes
|
||||
path = path.replace('\\', '/');
|
||||
|
||||
// Clean the path or illegal characters.
|
||||
final var specialChars = new char[]{
|
||||
'"', ':', '<', '>', '?', '|', // Sorted by ascii value (important)
|
||||
};
|
||||
|
||||
var cleanName = new StringBuilder();
|
||||
var allowedChars = allowWildcards ? specialCharsAllowWildcards : specialChars;
|
||||
for (var i = 0; i < path.length(); i++) {
|
||||
var c = path.charAt(i);
|
||||
if (c >= 32 && Arrays.binarySearch(specialChars, c) < 0 && (allowWildcards || c != '*')) {
|
||||
cleanName.append(c);
|
||||
}
|
||||
if (c >= 32 && Arrays.binarySearch(allowedChars, c) < 0) cleanName.append(c);
|
||||
}
|
||||
path = cleanName.toString();
|
||||
|
||||
|
@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
||||
return {}
|
||||
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.
|
||||
--
|
||||
-- The root filesystem "/" is considered a mount, along with disk folders and
|
||||
|
@ -87,6 +87,53 @@ describe("The fs library", function()
|
||||
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()
|
||||
it("removes . and ..", function()
|
||||
expect(fs.combine("./a/b")):eq("a/b")
|
||||
|
Loading…
Reference in New Issue
Block a user