feat: add File.glob for file pattern matching (issue 15)
Add File.glob(pattern) effect operation that returns a list of file paths matching a glob pattern (e.g., "src/**/*.lux"). Implemented across interpreter (using glob crate), JS backend (handler-based), and C backend (using POSIX glob.h). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -225,7 +225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -392,6 +392,12 @@ dependencies = [
|
|||||||
"wasip3",
|
"wasip3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.27"
|
version = "0.3.27"
|
||||||
@@ -772,6 +778,7 @@ dependencies = [
|
|||||||
name = "lux"
|
name = "lux"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"glob",
|
||||||
"lsp-server",
|
"lsp-server",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"postgres",
|
"postgres",
|
||||||
@@ -1182,7 +1189,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1475,7 +1482,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.1",
|
"getrandom 0.4.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ reqwest = { version = "0.11", default-features = false, features = ["blocking",
|
|||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||||
postgres = "0.19"
|
postgres = "0.19"
|
||||||
|
glob = "0.3"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1166,6 +1166,7 @@ impl CBackend {
|
|||||||
self.writeln(" void (*mkdir)(void* env, LuxString path);");
|
self.writeln(" void (*mkdir)(void* env, LuxString path);");
|
||||||
self.writeln(" void (*copy)(void* env, LuxString src, LuxString dst);");
|
self.writeln(" void (*copy)(void* env, LuxString src, LuxString dst);");
|
||||||
self.writeln(" LuxList* (*readDir)(void* env, LuxString path);");
|
self.writeln(" LuxList* (*readDir)(void* env, LuxString path);");
|
||||||
|
self.writeln(" LuxList* (*glob)(void* env, LuxString pattern);");
|
||||||
self.writeln(" void* env;");
|
self.writeln(" void* env;");
|
||||||
self.writeln("} LuxFileHandler;");
|
self.writeln("} LuxFileHandler;");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
@@ -1386,6 +1387,23 @@ impl CBackend {
|
|||||||
self.writeln(" return result;");
|
self.writeln(" return result;");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("#include <glob.h>");
|
||||||
|
self.writeln("static LuxList* lux_file_glob(LuxString pattern) {");
|
||||||
|
self.writeln(" LuxList* result = lux_list_new(16);");
|
||||||
|
self.writeln(" glob_t globbuf;");
|
||||||
|
self.writeln(" int ret = glob(pattern, 0, NULL, &globbuf);");
|
||||||
|
self.writeln(" if (ret == 0) {");
|
||||||
|
self.writeln(" for (size_t i = 0; i < globbuf.gl_pathc; i++) {");
|
||||||
|
self.writeln(" size_t len = strlen(globbuf.gl_pathv[i]);");
|
||||||
|
self.writeln(" LuxString name = (LuxString)lux_rc_alloc(len + 1, LUX_TAG_STRING);");
|
||||||
|
self.writeln(" memcpy(name, globbuf.gl_pathv[i], len + 1);");
|
||||||
|
self.writeln(" lux_list_push(result, (void*)name);");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" globfree(&globbuf);");
|
||||||
|
self.writeln(" }");
|
||||||
|
self.writeln(" return result;");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
self.writeln("static LuxString default_file_read(void* env, LuxString path) {");
|
self.writeln("static LuxString default_file_read(void* env, LuxString path) {");
|
||||||
self.writeln(" (void)env;");
|
self.writeln(" (void)env;");
|
||||||
self.writeln(" return lux_file_read(path);");
|
self.writeln(" return lux_file_read(path);");
|
||||||
@@ -1431,6 +1449,11 @@ impl CBackend {
|
|||||||
self.writeln(" return lux_file_readDir(path);");
|
self.writeln(" return lux_file_readDir(path);");
|
||||||
self.writeln("}");
|
self.writeln("}");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
|
self.writeln("static LuxList* default_file_glob(void* env, LuxString pattern) {");
|
||||||
|
self.writeln(" (void)env;");
|
||||||
|
self.writeln(" return lux_file_glob(pattern);");
|
||||||
|
self.writeln("}");
|
||||||
|
self.writeln("");
|
||||||
self.writeln("static LuxFileHandler default_file_handler = {");
|
self.writeln("static LuxFileHandler default_file_handler = {");
|
||||||
self.writeln(" .read = default_file_read,");
|
self.writeln(" .read = default_file_read,");
|
||||||
self.writeln(" .write = default_file_write,");
|
self.writeln(" .write = default_file_write,");
|
||||||
@@ -1441,6 +1464,7 @@ impl CBackend {
|
|||||||
self.writeln(" .mkdir = default_file_mkdir,");
|
self.writeln(" .mkdir = default_file_mkdir,");
|
||||||
self.writeln(" .copy = default_file_copy,");
|
self.writeln(" .copy = default_file_copy,");
|
||||||
self.writeln(" .readDir = default_file_readDir,");
|
self.writeln(" .readDir = default_file_readDir,");
|
||||||
|
self.writeln(" .glob = default_file_glob,");
|
||||||
self.writeln(" .env = NULL");
|
self.writeln(" .env = NULL");
|
||||||
self.writeln("};");
|
self.writeln("};");
|
||||||
self.writeln("");
|
self.writeln("");
|
||||||
@@ -3790,6 +3814,17 @@ impl CBackend {
|
|||||||
self.register_rc_var(&temp, "LuxList*");
|
self.register_rc_var(&temp, "LuxList*");
|
||||||
return Ok(temp);
|
return Ok(temp);
|
||||||
}
|
}
|
||||||
|
"glob" => {
|
||||||
|
let pattern = self.emit_expr(&args[0])?;
|
||||||
|
let temp = format!("_glob_{}", self.fresh_name());
|
||||||
|
if self.has_evidence {
|
||||||
|
self.writeln(&format!("LuxList* {} = ev->file->glob(ev->file->env, {});", temp, pattern));
|
||||||
|
} else {
|
||||||
|
self.writeln(&format!("LuxList* {} = lux_file_glob({});", temp, pattern));
|
||||||
|
}
|
||||||
|
self.register_rc_var(&temp, "LuxList*");
|
||||||
|
return Ok(temp);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5353,7 +5388,7 @@ impl CBackend {
|
|||||||
"read" => Some("LuxString".to_string()),
|
"read" => Some("LuxString".to_string()),
|
||||||
"write" | "append" | "delete" | "mkdir" | "copy" => Some("void".to_string()),
|
"write" | "append" | "delete" | "mkdir" | "copy" => Some("void".to_string()),
|
||||||
"exists" | "isDir" => Some("LuxBool".to_string()),
|
"exists" | "isDir" => Some("LuxBool".to_string()),
|
||||||
"readDir" | "listDir" => Some("LuxList*".to_string()),
|
"readDir" | "listDir" | "glob" => Some("LuxList*".to_string()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else if effect.name == "Http" {
|
} else if effect.name == "Http" {
|
||||||
|
|||||||
@@ -3968,6 +3968,29 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
("File", "glob") => {
|
||||||
|
let pattern = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.glob requires a string pattern".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match glob::glob(&pattern) {
|
||||||
|
Ok(paths) => {
|
||||||
|
let entries: Vec<Value> = paths
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.map(|path| Value::String(path.to_string_lossy().to_string()))
|
||||||
|
.collect();
|
||||||
|
Ok(Value::List(entries))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Invalid glob pattern '{}': {}", pattern, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== Process Effect =====
|
// ===== Process Effect =====
|
||||||
("Process", "exec") => {
|
("Process", "exec") => {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|||||||
@@ -964,6 +964,11 @@ impl TypeEnv {
|
|||||||
],
|
],
|
||||||
return_type: Type::Unit,
|
return_type: Type::Unit,
|
||||||
},
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "glob".to_string(),
|
||||||
|
params: vec![("pattern".to_string(), Type::String)],
|
||||||
|
return_type: Type::List(Box::new(Type::String)),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user