Liquid create tag that uses array of values passed to the render function (latest, limit, tag)
examples/liquid/tag-with-attribute-and-value-and-another-optional-pair/Cargo.toml
[package] name = "parse" version = "0.1.0" edition = "2021" [dependencies] liquid = "0.26.9" liquid-core = "0.26.9" serde = { version = "1.0.215", features = ["derive"] }
examples/liquid/tag-with-attribute-and-value-and-another-optional-pair/src/latest_tag.rs
use std::io::Write; use liquid_core::error::ResultLiquidReplaceExt; use liquid_core::model::Scalar; use liquid_core::parser::TryMatchToken; use liquid_core::Language; use liquid_core::Renderable; use liquid_core::Result; use liquid_core::Runtime; use liquid_core::ValueView; use liquid_core::{ParseTag, TagReflection, TagTokenIter}; use serde::Serialize; #[derive(Copy, Clone, Debug, Default)] pub struct LatestTag; impl TagReflection for LatestTag { fn tag(&self) -> &'static str { "latest" } fn description(&self) -> &'static str { "" } } impl ParseTag for LatestTag { fn parse( &self, mut arguments: TagTokenIter<'_>, _options: &Language, ) -> Result<Box<dyn Renderable>> { arguments .expect_next("limit expected")? .expect_str("limit") .into_result_custom_msg("limit expected.")?; arguments .expect_next("Assignment operator \"=\" expected.")? .expect_str("=") .into_result_custom_msg("Assignment operator \"=\" expected.")?; let token = arguments.expect_next("Identifier or value expected")?; let value = match token.expect_literal() { TryMatchToken::Matches(name) => name.to_kstr().to_string(), TryMatchToken::Fails(name) => return name.raise_error().into_err(), }; let limit = match value.parse::<u8>() { Ok(value) => value, Err(_) => return Err(liquid_core::error::Error::with_msg("Expected number")), }; let key = arguments.next(); let tag = match key { Some(token) => { token .expect_str("tag") .into_result_custom_msg("expected tag")?; arguments .expect_next("Assignment operator \"=\" expected.")? .expect_str("=") .into_result_custom_msg("Assignment operator \"=\" expected.")?; let literal = arguments.expect_next("value of tag")?.expect_literal(); let value = match literal { TryMatchToken::Matches(name) => name.to_kstr().to_string(), TryMatchToken::Fails(name) => return name.raise_error().into_err(), }; Some(value) } None => None, }; arguments.expect_nothing()?; Ok(Box::new(Latest { limit, tag })) } fn reflection(&self) -> &dyn TagReflection { self } } #[derive(Debug)] struct Latest { limit: u8, tag: Option<String>, } impl Renderable for Latest { fn render_to(&self, writer: &mut dyn Write, runtime: &dyn Runtime) -> Result<()> { let mut count = 0; let selected_tag = self.tag.clone().unwrap_or_default(); match runtime.get(&[Scalar::new("items")]) { // Ok(values) => values.as_array().unwrap().values().collect::<Vec<_>>(), Ok(values) => { for val in values.as_array().unwrap().values() { let obj = val.as_object().unwrap(); let text = obj.get("text").unwrap().to_kstr().to_string(); let tag = obj.get("tag").unwrap().to_kstr().to_string(); if self.tag.is_some() && tag != selected_tag { continue; } write!(writer, "<li>{} ({})</li>", text, tag).replace("Failed to render")?; count += 1; if count >= self.limit { break; } } } Err(_) => return Err(liquid_core::error::Error::with_msg("Expected number")), }; //println!("{:?}", things[0]); //println!("text: {:?}", things[0].as_object().unwrap().get("text")); Ok(()) } } #[derive(Debug, Serialize)] pub struct Item<'a> { text: &'a str, id: u32, tag: &'a str, } impl<'a> Item<'a> { pub fn new(text: &'a str, id: u32, tag: &'a str) -> Self { Self { text, id, tag } } } #[cfg(test)] mod test { use super::*; fn get_items() -> &'static [Item<'static>] { let items = &[ Item { text: "one", id: 1, tag: "web", }, Item { text: "two", id: 2, tag: "programming", }, Item { text: "three", id: 3, tag: "web", }, Item { text: "four", id: 4, tag: "programming", }, Item { text: "five", id: 5, tag: "web", }, Item { text: "six", id: 6, tag: "programming", }, Item { text: "seven", id: 7, tag: "web", }, Item { text: "eight", id: 8, tag: "programming", }, Item { text: "nine", id: 9, tag: "web", }, Item { text: "ten", id: 10, tag: "web", }, ]; items } use liquid_core::object; use liquid_core::parser; use liquid_core::runtime; use liquid_core::runtime::RuntimeBuilder; use liquid_core::Value; fn options() -> Language { let mut options = Language::default(); options .tags .register("latest".to_string(), LatestTag.into()); options } #[test] fn latest_5() { let options = options(); let template = parser::parse(r#"{% latest limit=5 %}"#, &options) .map(runtime::Template::new) .unwrap(); let runtime = RuntimeBuilder::new().build(); let objects = get_items() .iter() .map(|item| { let obj = object!({"text": item.text, "id": item.id, "tag": item.tag}); Value::Object(obj) }) .collect::<Vec<_>>(); runtime.set_global("items".into(), Value::Array(objects)); let output = template.render(&runtime).unwrap(); assert_eq!( output, r#"<li>one (web)</li><li>two (programming)</li><li>three (web)</li><li>four (programming)</li><li>five (web)</li>"# ); } #[test] fn latest_5_web() { let options = options(); let template = parser::parse(r#"{% latest limit=5 tag="web" %}"#, &options) .map(runtime::Template::new) .unwrap(); let runtime = RuntimeBuilder::new().build(); let objects = get_items() .iter() .map(|item| { let obj = object!({"text": item.text, "id": item.id, "tag": item.tag}); Value::Object(obj) }) .collect::<Vec<_>>(); runtime.set_global("items".into(), Value::Array(objects)); let output = template.render(&runtime).unwrap(); assert_eq!( output, r#"<li>one (web)</li><li>three (web)</li><li>five (web)</li><li>seven (web)</li><li>nine (web)</li>"# ); } }
examples/liquid/tag-with-attribute-and-value-and-another-optional-pair/src/main.rs
mod latest_tag; fn main() { let template = liquid::ParserBuilder::with_stdlib() .tag(latest_tag::LatestTag) .build() .unwrap() .parse(r#"{% latest limit=5 %}"#) .unwrap(); let items = &[latest_tag::Item::new("one", 1, "web")]; let globals = liquid::object!({"items": items}); let output = template.render(&globals).unwrap(); assert_eq!(output, r#"<li>one (web)</li>"#); }