feat: builtins.readType & builtins.readDir
This commit is contained in:
@@ -292,8 +292,14 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => {
|
|||||||
return { outPath: fetchurl(attrs) };
|
return { outPath: fetchurl(attrs) };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readDir = (path: NixValue): never => {
|
export const readDir = (path: NixValue): NixAttrs => {
|
||||||
throw new Error("Not implemented: readDir");
|
const pathStr = coerceToPath(path);
|
||||||
|
const entries: Record<string, string> = Deno.core.ops.op_read_dir(pathStr);
|
||||||
|
const result: NixAttrs = {};
|
||||||
|
for (const [name, type] of Object.entries(entries)) {
|
||||||
|
result[name] = type;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readFile = (path: NixValue): string => {
|
export const readFile = (path: NixValue): string => {
|
||||||
@@ -301,8 +307,9 @@ export const readFile = (path: NixValue): string => {
|
|||||||
return Deno.core.ops.op_read_file(pathStr);
|
return Deno.core.ops.op_read_file(pathStr);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readFileType = (path: NixValue): never => {
|
export const readFileType = (path: NixValue): string => {
|
||||||
throw new Error("Not implemented: readFileType");
|
const pathStr = coerceToPath(path);
|
||||||
|
return Deno.core.ops.op_read_file_type(pathStr);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pathExists = (path: NixValue): boolean => {
|
export const pathExists = (path: NixValue): boolean => {
|
||||||
|
|||||||
2
nix-js/runtime-ts/src/types/global.d.ts
vendored
2
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -38,6 +38,8 @@ declare global {
|
|||||||
function op_resolve_path(currentDir: string, path: string): string;
|
function op_resolve_path(currentDir: string, path: string): string;
|
||||||
function op_import(path: string): string;
|
function op_import(path: string): string;
|
||||||
function op_read_file(path: string): string;
|
function op_read_file(path: string): string;
|
||||||
|
function op_read_file_type(path: string): string;
|
||||||
|
function op_read_dir(path: string): Record<string, string>;
|
||||||
function op_path_exists(path: string): boolean;
|
function op_path_exists(path: string): boolean;
|
||||||
function op_sha256_hex(data: string): string;
|
function op_sha256_hex(data: string): string;
|
||||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
|||||||
let mut ops = vec![
|
let mut ops = vec![
|
||||||
op_import::<Ctx>(),
|
op_import::<Ctx>(),
|
||||||
op_read_file(),
|
op_read_file(),
|
||||||
|
op_read_file_type(),
|
||||||
|
op_read_dir(),
|
||||||
op_path_exists(),
|
op_path_exists(),
|
||||||
op_resolve_path(),
|
op_resolve_path(),
|
||||||
op_sha256_hex(),
|
op_sha256_hex(),
|
||||||
@@ -163,6 +165,64 @@ fn op_path_exists(#[string] path: String) -> bool {
|
|||||||
std::path::Path::new(&path).exists()
|
std::path::Path::new(&path).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
fn op_read_file_type(#[string] path: String) -> std::result::Result<String, NixError> {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
let metadata = std::fs::symlink_metadata(path)
|
||||||
|
.map_err(|e| format!("Failed to read file type for {}: {}", path.display(), e))?;
|
||||||
|
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
let type_str = if file_type.is_dir() {
|
||||||
|
"directory"
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
"regular"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(type_str.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[serde]
|
||||||
|
fn op_read_dir(#[string] path: String) -> std::result::Result<std::collections::HashMap<String, String>, NixError> {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
|
||||||
|
if !path.is_dir() {
|
||||||
|
return Err(format!("{} is not a directory", path.display()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries = std::fs::read_dir(path)
|
||||||
|
.map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?;
|
||||||
|
|
||||||
|
let mut result = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
||||||
|
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
let file_type = entry.file_type()
|
||||||
|
.map_err(|e| format!("Failed to read file type for {}: {}", entry.path().display(), e))?;
|
||||||
|
|
||||||
|
let type_str = if file_type.is_dir() {
|
||||||
|
"directory"
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
"regular"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
result.insert(file_name, type_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
fn op_resolve_path(
|
fn op_resolve_path(
|
||||||
|
|||||||
@@ -260,3 +260,103 @@ fn path_deterministic() {
|
|||||||
// Same inputs should produce same store path
|
// Same inputs should produce same store path
|
||||||
assert_eq!(result1, result2);
|
assert_eq!(result1, result2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_file_type_regular_file() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_file = temp_dir.path().join("test.txt");
|
||||||
|
std::fs::write(&test_file, "Test content").unwrap();
|
||||||
|
|
||||||
|
let expr = format!(r#"builtins.readFileType {}"#, test_file.display());
|
||||||
|
assert_eq!(eval(&expr), Value::String("regular".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_file_type_directory() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_dir = temp_dir.path().join("testdir");
|
||||||
|
std::fs::create_dir(&test_dir).unwrap();
|
||||||
|
|
||||||
|
let expr = format!(r#"builtins.readFileType {}"#, test_dir.display());
|
||||||
|
assert_eq!(eval(&expr), Value::String("directory".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_file_type_symlink() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let target = temp_dir.path().join("target.txt");
|
||||||
|
let symlink = temp_dir.path().join("link.txt");
|
||||||
|
|
||||||
|
std::fs::write(&target, "Target content").unwrap();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&target, &symlink).unwrap();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let expr = format!(r#"builtins.readFileType {}"#, symlink.display());
|
||||||
|
assert_eq!(eval(&expr), Value::String("symlink".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_dir_basic() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_dir = temp_dir.path().join("readdir_test");
|
||||||
|
std::fs::create_dir(&test_dir).unwrap();
|
||||||
|
|
||||||
|
std::fs::write(test_dir.join("file1.txt"), "Content 1").unwrap();
|
||||||
|
std::fs::write(test_dir.join("file2.txt"), "Content 2").unwrap();
|
||||||
|
std::fs::create_dir(test_dir.join("subdir")).unwrap();
|
||||||
|
|
||||||
|
let expr = format!(r#"builtins.readDir {}"#, test_dir.display());
|
||||||
|
let result = eval(&expr);
|
||||||
|
|
||||||
|
if let Value::AttrSet(attrs) = result {
|
||||||
|
assert_eq!(attrs.get("file1.txt"), Some(&Value::String("regular".to_string())));
|
||||||
|
assert_eq!(attrs.get("file2.txt"), Some(&Value::String("regular".to_string())));
|
||||||
|
assert_eq!(attrs.get("subdir"), Some(&Value::String("directory".to_string())));
|
||||||
|
assert_eq!(attrs.len(), 3);
|
||||||
|
} else {
|
||||||
|
panic!("Expected AttrSet, got {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_dir_empty() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_dir = temp_dir.path().join("empty_dir");
|
||||||
|
std::fs::create_dir(&test_dir).unwrap();
|
||||||
|
|
||||||
|
let expr = format!(r#"builtins.readDir {}"#, test_dir.display());
|
||||||
|
let result = eval(&expr);
|
||||||
|
|
||||||
|
if let Value::AttrSet(attrs) = result {
|
||||||
|
assert_eq!(attrs.len(), 0);
|
||||||
|
} else {
|
||||||
|
panic!("Expected AttrSet, got {:?}", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_dir_nonexistent_fails() {
|
||||||
|
let expr = r#"builtins.readDir "/nonexistent/directory""#;
|
||||||
|
let result = eval_result(expr);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_dir_on_file_fails() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let test_file = temp_dir.path().join("test.txt");
|
||||||
|
std::fs::write(&test_file, "Test content").unwrap();
|
||||||
|
|
||||||
|
let expr = format!(r#"builtins.readDir {}"#, test_file.display());
|
||||||
|
let result = eval_result(&expr);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
let err_msg = result.unwrap_err().to_string();
|
||||||
|
assert!(err_msg.contains("not a directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user