about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
Diffstat (limited to 'tvix')
-rw-r--r--tvix/eval/src/builtins/impure.rs41
-rw-r--r--tvix/eval/src/errors.rs26
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix1
-rw-r--r--tvix/eval/src/tests/tvix_tests/readDir/bar0
-rw-r--r--tvix/eval/src/tests/tvix_tests/readDir/foo/.keep0
-rw-r--r--tvix/eval/src/value/mod.rs6
7 files changed, 73 insertions, 2 deletions
diff --git a/tvix/eval/src/builtins/impure.rs b/tvix/eval/src/builtins/impure.rs
index 8e23003edb47..32653b665c36 100644
--- a/tvix/eval/src/builtins/impure.rs
+++ b/tvix/eval/src/builtins/impure.rs
@@ -1,6 +1,7 @@
 use std::{
     cell::RefCell,
     collections::{BTreeMap, HashMap},
+    io,
     rc::Rc,
     time::{SystemTime, UNIX_EPOCH},
 };
@@ -8,13 +9,49 @@ use std::{
 use crate::{
     errors::ErrorKind,
     observer::NoOpObserver,
-    value::{Builtin, NixString, Thunk},
+    value::{Builtin, NixAttrs, NixString, Thunk},
     vm::VM,
     SourceCode, Value,
 };
 
 fn impure_builtins() -> Vec<Builtin> {
-    vec![]
+    vec![Builtin::new(
+        "readDir",
+        &[true],
+        |args: Vec<Value>, vm: &mut VM| {
+            let path = super::coerce_value_to_path(&args[0], vm)?;
+            let mk_err = |err: io::Error| ErrorKind::IO {
+                path: Some(path.clone()),
+                error: Rc::new(err),
+            };
+
+            let mut res = BTreeMap::new();
+            for entry in path.read_dir().map_err(mk_err)? {
+                let entry = entry.map_err(mk_err)?;
+                let file_type = entry
+                    .metadata()
+                    .map_err(|err| ErrorKind::IO {
+                        path: Some(entry.path()),
+                        error: Rc::new(err),
+                    })?
+                    .file_type();
+                let val = if file_type.is_dir() {
+                    "directory"
+                } else if file_type.is_file() {
+                    "regular"
+                } else if file_type.is_symlink() {
+                    "symlink"
+                } else {
+                    "unknown"
+                };
+                res.insert(
+                    entry.file_name().to_string_lossy().as_ref().into(),
+                    val.into(),
+                );
+            }
+            Ok(Value::attrs(NixAttrs::from_map(res)))
+        },
+    )]
 }
 
 /// Return all impure builtins, that is all builtins which may perform I/O
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index a2f09e98fb2e..a7d54d51bb27 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -1,5 +1,6 @@
 use crate::spans::ToSpan;
 use crate::value::CoercionKind;
+use std::io;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
@@ -121,6 +122,12 @@ pub enum ErrorKind {
         errors: Vec<Error>,
     },
 
+    /// I/O errors
+    IO {
+        path: Option<PathBuf>,
+        error: Rc<io::Error>,
+    },
+
     /// Tvix internal warning for features triggered by users that are
     /// not actually implemented yet, and without which eval can not
     /// proceed.
@@ -142,6 +149,15 @@ impl From<Error> for ErrorKind {
     }
 }
 
+impl From<io::Error> for ErrorKind {
+    fn from(e: io::Error) -> Self {
+        ErrorKind::IO {
+            path: None,
+            error: Rc::new(e),
+        }
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Error {
     pub kind: ErrorKind,
@@ -306,6 +322,14 @@ to a missing value in the attribute set(s) included via `with`."#,
                 )
             }
 
+            ErrorKind::IO { path, error } => {
+                write!(f, "I/O error: ")?;
+                if let Some(path) = path {
+                    write!(f, "{}: ", path.display())?;
+                }
+                write!(f, "{error}")
+            }
+
             ErrorKind::NotImplemented(feature) => {
                 write!(f, "feature not yet implemented in Tvix: {}", feature)
             }
@@ -583,6 +607,7 @@ impl Error {
             | ErrorKind::ReadFileError { .. }
             | ErrorKind::ImportParseError { .. }
             | ErrorKind::ImportCompilerError { .. }
+            | ErrorKind::IO { .. }
             | ErrorKind::NotImplemented(_) => return None,
         };
 
@@ -620,6 +645,7 @@ impl Error {
             ErrorKind::ReadFileError { .. } => "E026",
             ErrorKind::ImportParseError { .. } => "E027",
             ErrorKind::ImportCompilerError { .. } => "E028",
+            ErrorKind::IO { .. } => "E029",
 
             // Placeholder error while Tvix is under construction.
             ErrorKind::NotImplemented(_) => "E999",
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp
new file mode 100644
index 000000000000..bf8d2c14ea4f
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.exp
@@ -0,0 +1 @@
+{ bar = "regular"; foo = "directory"; }
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix
new file mode 100644
index 000000000000..a7ec9292aae2
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-readDir.nix
@@ -0,0 +1 @@
+builtins.readDir ./readDir
diff --git a/tvix/eval/src/tests/tvix_tests/readDir/bar b/tvix/eval/src/tests/tvix_tests/readDir/bar
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/readDir/bar
diff --git a/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep b/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/readDir/foo/.keep
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index d3b981b78cba..0f3b9cbbf4a2 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -371,6 +371,12 @@ impl From<i64> for Value {
     }
 }
 
+impl From<&str> for Value {
+    fn from(val: &str) -> Self {
+        Self::String(val.into())
+    }
+}
+
 fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
     ErrorKind::TypeError {
         expected,