Repository: tweag/nickel (rust)

Issue:
IMPORT_PATH for pyckel
**Is your feature request related to a problem? Please describe.**
There is no way to specify import path when using the pyckel wraper because the `NICKEL_IMPORT_PATH` related code was implemented in the cli part of the rust app.

**Describe the solution you'd like**
A great step would be for the pyckel wrapper to honor the `NICKEL_IMPORT_PATH` environment variable.

And as a great bonus we could add an `import` optional argument for the `pyckel.run` function to be able to specify the import directory at runtime.

Pull request description:
Add import_paths parameter to Python bindings
Fixes #2137.

py-nickel doesn't honor the environment variable `NICKEL_IMPORT_PATH` nor the CLI argument `--import-path` (obviously, as it doesn't have access to the latter). This is on purpose, but this means there were no way to specify additional import paths when running Nickel from Python, which is is problem.

This commit adds an optional parameter `import_paths`, which is a list of paths, that can be provided to the `run` function on the Python side.

Patch:
diff --git a/core/src/pretty.rs b/core/src/pretty.rs
index 50077d93..d0d78bb4 100644
--- a/core/src/pretty.rs
+++ b/core/src/pretty.rs
@@ -522,6 +522,7 @@ impl Allocator {
         match id_expr.as_ref() {
             // Nickel will not parse a multiline string literal in this position
             Term::StrChunks(chunks) => self.chunks(chunks, StringRenderStyle::ForceMonoline),
+            Term::ParseError(_) => docs![self, "<parse error>"],
             _ => unimplemented!("Dynamic record fields must be StrChunks currently"),
         }
         .append(self.field_body(field))
diff --git a/py-nickel/src/lib.rs b/py-nickel/src/lib.rs
index e983b3ef..080838e7 100644
--- a/py-nickel/src/lib.rs
+++ b/py-nickel/src/lib.rs
@@ -1,3 +1,4 @@
+use std::ffi::OsString;
 use std::io::Cursor;
 
 use nickel_lang_core::{
@@ -24,10 +25,27 @@ fn error_to_exception<E: Into<Error>, EC: Cache>(error: E, program: &mut Program
 }
 
 /// Evaluate from a Python str of a Nickel expression to a Python str of the resulting JSON.
+///
+/// # Parameters
+///
+/// - `expr`: the Nickel expression to evaluate.
+/// - `import_paths`: optional list of paths to search for imported files. In the Nickel
+///   stand-alone binary, the import paths are controlled by the `NICKEL_IMPORT_PATH` environment
+///   variable and the `--import-path` CLI argument. In the Python bindings, you need to provide
+///   them explicitly instead.
 #[pyfunction]
-pub fn run(s: String) -> PyResult<String> {
-    let mut program: Program<CacheImpl> =
-        Program::new_from_source(Cursor::new(s), "python", std::io::sink(), NullReporter {})?;
+#[pyo3(signature = (expr, import_paths=None))]
+pub fn run(expr: String, import_paths: Option<Vec<OsString>>) -> PyResult<String> {
+    let mut program: Program<CacheImpl> = Program::new_from_source(
+        Cursor::new(expr),
+        "python",
+        std::io::sink(),
+        NullReporter {},
+    )?;
+
+    if let Some(import_paths) = import_paths {
+        program.add_import_paths(import_paths.into_iter());
+    }
 
     let term = program
         .eval_full()


Test patch:
diff --git a/lsp/nls/tests/inputs/no-crash-on-pretty-print.ncl b/lsp/nls/tests/inputs/no-crash-on-pretty-print.ncl
new file mode 100644
index 00000000..2d04e81c
--- /dev/null
+++ b/lsp/nls/tests/inputs/no-crash-on-pretty-print.ncl
@@ -0,0 +1,7 @@
+### /main.ncl
+{
+  multiple_of
+    : Number -> Dyn -> [| 'Ok, 'Error { message | String, notes] } |]
+    = 1
+}
+### diagnostic = ["file:///main.ncl"]
diff --git a/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__no-crash-on-pretty-print.ncl.snap b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__no-crash-on-pretty-print.ncl.snap
new file mode 100644
index 00000000..6de4221f
--- /dev/null
+++ b/lsp/nls/tests/snapshots/main__lsp__nls__tests__inputs__no-crash-on-pretty-print.ncl.snap
@@ -0,0 +1,11 @@
+---
+source: lsp/nls/tests/main.rs
+expression: output
+---
+(file:///main.ncl, 2:63-2:64: )
+(file:///main.ncl, 2:63-2:64: unexpected token)
+(file:///main.ncl, 3:6-3:7: incompatible types
+Expected an expression of type `Number -> Dyn -> [| 'Ok, 'Error { message | String, <parse error>, } |]`
+Found an expression of type `Number`
+These types are not compatible)
+(file:///main.ncl, 3:6-3:7: this expression)