From c7f699fef8ca499d86e30c09a149d1f5fa60d09f Mon Sep 17 00:00:00 2001 From: Swarnim Arun Date: Fri, 12 Jan 2024 19:55:01 +0530 Subject: [PATCH] feat: add `load_from_file` jinja function (#468) --- Cargo.lock | 40 ++++++++++++++++++++++++- Cargo.toml | 3 +- src/packaging.rs | 2 +- src/recipe/jinja.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18b8c9f47..9c8750534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2396,7 +2396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -2601,6 +2601,7 @@ dependencies = [ "thiserror", "tokio", "tokio-util", + "toml", "tracing", "tracing-core", "tracing-indicatif", @@ -3278,6 +3279,15 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3807,11 +3817,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3824,6 +3849,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index e003b85dd..68262138c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,10 +96,11 @@ zip = { version = "0.6.6", default-features = false, features = [ "deflate", ] } bzip2 = "0.4.4" +base64 = "0.21.6" flate2 = "1.0.28" xz2 = "0.1.7" zstd = "0.13.0" -base64 = "0.21.6" +toml = "0.8.8" [dev-dependencies] insta = { version = "1.34.0", features = ["yaml"] } diff --git a/src/packaging.rs b/src/packaging.rs index c4acf1b8e..01c6de1a6 100644 --- a/src/packaging.rs +++ b/src/packaging.rs @@ -133,7 +133,7 @@ fn contains_prefix_text(file_path: &Path, prefix: &Path) -> Result std::borrow::Cow<'_, str> { +pub(crate) fn to_forward_slash_lossy(path: &Path) -> std::borrow::Cow<'_, str> { #[cfg(target_os = "windows")] { let mut buf = String::new(); diff --git a/src/recipe/jinja.rs b/src/recipe/jinja.rs index f064e2c79..779e28f77 100644 --- a/src/recipe/jinja.rs +++ b/src/recipe/jinja.rs @@ -178,6 +178,7 @@ fn set_jinja(config: &SelectorConfig) -> minijinja::Environment<'static> { target_platform, build_platform, variant, + experimental, .. } = config.clone(); env.add_function("cdt", move |package_name: String| { @@ -241,6 +242,37 @@ fn set_jinja(config: &SelectorConfig) -> minijinja::Environment<'static> { format!("{}{}", major, minor) }); + env.add_function("load_from_file", move |path: String| { + if !experimental { + return Err(minijinja::Error::new( + minijinja::ErrorKind::InvalidOperation, + "Experimental feature: provide the `--experimental` flag to enable this feature", + )); + } + let src = std::fs::read_to_string(&path).map_err(|e| { + minijinja::Error::new(minijinja::ErrorKind::UndefinedError, e.to_string()) + })?; + // tracing::info!("loading from path: {path}"); + let filename = path + .split('/') + .last() + .expect("unreachable: split will always atleast return empty string"); + // tracing::info!("loading filename: {filename}"); + let value: minijinja::Value = match filename.split_once('.') { + Some((_, "yaml")) | Some((_, "yml")) => serde_yaml::from_str(&src).map_err(|e| { + minijinja::Error::new(minijinja::ErrorKind::CannotDeserialize, e.to_string()) + })?, + Some((_, "json")) => serde_json::from_str(&src).map_err(|e| { + minijinja::Error::new(minijinja::ErrorKind::CannotDeserialize, e.to_string()) + })?, + Some((_, "toml")) => toml::from_str(&src).map_err(|e| { + minijinja::Error::new(minijinja::ErrorKind::CannotDeserialize, e.to_string()) + })?, + _ => Value::from(src), + }; + Ok(value) + }); + env } @@ -557,6 +589,8 @@ mod tests { use rattler_conda_types::Platform; + use crate::packaging::to_forward_slash_lossy; + use super::*; fn with_temp_dir(key: &'static str, f: impl Fn(&std::path::Path)) { @@ -633,6 +667,44 @@ mod tests { }); } + #[test] + #[rustfmt::skip] + fn eval_load_from_file() { + let options = SelectorConfig { + target_platform: Platform::Linux64, + build_platform: Platform::Linux64, + experimental: true, + ..Default::default() + }; + + let jinja = Jinja::new(options); + + let temp_dir = tempfile::tempdir().unwrap(); + let path = temp_dir.path().join("test.json"); + let path_str = to_forward_slash_lossy(&path); + std::fs::write(&path, "{ \"hello\": \"world\" }").unwrap(); + assert_eq!( + jinja.eval(&format!("load_from_file('{}')['hello']", path_str)).expect("test 1").as_str(), + Some("world"), + ); + + let path = temp_dir.path().join("test.yaml"); + std::fs::write(&path, "hello: world").unwrap(); + let path_str = to_forward_slash_lossy(&path); + assert_eq!( + jinja.eval(&format!("load_from_file('{}')['hello']", path_str)).expect("test 2").as_str(), + Some("world"), + ); + + let path = temp_dir.path().join("test.toml"); + let path_str = to_forward_slash_lossy(&path); + std::fs::write(&path, "hello = 'world'").unwrap(); + assert_eq!( + jinja.eval(&format!("load_from_file('{}')['hello']", path_str)).expect("test 2").as_str(), + Some("world"), + ); + } + #[test] #[rustfmt::skip] fn eval() {