diff --git a/Cargo.lock b/Cargo.lock index 7c3dbd0c1..5fc44e4f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -466,6 +472,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + [[package]] name = "heck" version = "0.4.1" @@ -512,7 +524,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", ] [[package]] @@ -641,6 +663,7 @@ name = "liquid-core" version = "0.26.4" dependencies = [ "anymap2", + "indexmap 2.1.0", "itertools 0.12.0", "kstring", "liquid-derive", @@ -980,7 +1003,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "indexmap", + "indexmap 1.9.3", "ryu", "serde", "yaml-rust", diff --git a/Cargo.toml b/Cargo.toml index 5e6b550c9..d7d34f2ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ pre-release-replacements = [ [features] default = ["stdlib"] stdlib = ["liquid-lib/stdlib"] +indexmap = ["liquid-core/indexmap"] [dependencies] doc-comment = "0.3" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 91db02234..9e2f8a85f 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -24,6 +24,7 @@ num-traits = "0.2" pest = "2.0" pest_derive = "2.0" regex = "1.5" +indexmap = { version = "2.1.0", optional = true } # Exposed in API time = { version = "0.3", default-features = false, features = ["formatting", "macros", "parsing"] } @@ -38,3 +39,4 @@ snapbox = "0.4.14" [features] default = [] derive = ["liquid-derive"] +indexmap = ["dep:indexmap"] diff --git a/crates/core/src/model/object/mod.rs b/crates/core/src/model/object/mod.rs index 68a217554..3925ea843 100644 --- a/crates/core/src/model/object/mod.rs +++ b/crates/core/src/model/object/mod.rs @@ -3,6 +3,9 @@ pub mod map; mod ser; +#[cfg(feature = "indexmap")] +use indexmap::IndexMap; + use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; @@ -358,6 +361,79 @@ impl<'s, O: ObjectView> fmt::Display for ObjectRender<'s, O> { } } +#[cfg(feature = "indexmap")] +impl ValueView for IndexMap { + fn as_debug(&self) -> &dyn fmt::Debug { + self + } + + fn render(&self) -> DisplayCow<'_> { + DisplayCow::Owned(Box::new(ObjectRender { s: self })) + } + fn source(&self) -> DisplayCow<'_> { + DisplayCow::Owned(Box::new(ObjectSource { s: self })) + } + fn type_name(&self) -> &'static str { + "object" + } + fn query_state(&self, state: State) -> bool { + match state { + State::Truthy => true, + State::DefaultValue | State::Empty | State::Blank => self.is_empty(), + } + } + + fn to_kstr(&self) -> KStringCow<'_> { + let s = ObjectRender { s: self }.to_string(); + KStringCow::from_string(s) + } + fn to_value(&self) -> Value { + Value::Object( + self.iter() + .map(|(k, v)| (crate::model::KString::from_ref(k.as_index()), v.to_value())) + .collect(), + ) + } + + fn as_object(&self) -> Option<&dyn ObjectView> { + Some(self) + } +} + +#[cfg(feature = "indexmap")] +impl ObjectView for IndexMap { + fn as_value(&self) -> &dyn ValueView { + self + } + + fn size(&self) -> i64 { + self.len() as i64 + } + + fn keys<'k>(&'k self) -> Box> + 'k> { + let keys = IndexMap::keys(self).map(|s| s.as_index().into()); + Box::new(keys) + } + + fn values<'k>(&'k self) -> Box + 'k> { + let i = IndexMap::values(self).map(as_view); + Box::new(i) + } + + fn iter<'k>(&'k self) -> Box, &'k dyn ValueView)> + 'k> { + let i = IndexMap::iter(self).map(|(k, v)| (k.as_index().into(), as_view(v))); + Box::new(i) + } + + fn contains_key(&self, index: &str) -> bool { + IndexMap::contains_key(self, index) + } + + fn get<'s>(&'s self, index: &str) -> Option<&'s dyn ValueView> { + IndexMap::get(self, index).map(as_view) + } +} + #[cfg(test)] mod test { use super::*;