From 61b54e8f6b177f33a1428ab6a3622503a63cc1d6 Mon Sep 17 00:00:00 2001 From: Mike Buland Date: Tue, 9 Jun 2026 14:02:53 -0700 Subject: Identifiers work! --- src/lib.rs | 132 +++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 89 insertions(+), 43 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 7790b9a..a2bfc37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,14 +38,14 @@ struct View { } impl View { - fn process(&self, input: &Value) -> Result { - let mut out_vars = HashMap::new(); + fn process(&self, input: &impl MappedStructure) -> Result { + let mut out_vars = Context::new(); for output in &self.outputs { let mut buf = String::new(); exec( input, &output.code, &mut buf )?; out_vars.insert( output.name.clone(), Value::String(buf) ); } - Ok(Value::Dictionary(out_vars)) + Ok(out_vars) } } @@ -59,17 +59,60 @@ type Properties = HashMap; #[derive(Debug)] enum Token { - Show(String,Properties), + Show(Identifier,Properties), Text(String), } -#[derive(Debug)] +#[derive(Debug,Clone)] pub enum Value { Dictionary(HashMap), List(Vec), String(String), Int(i64), Float(f64), + Identifier(Identifier), +} + +#[derive(Debug,Clone)] +pub enum IdentifierValue { + Name(String), + Index(usize), +} + +pub type Identifier = Vec; +pub type Context = HashMap; + +pub trait MappedStructure { + fn get_value(&self, id: &Identifier) -> Option; +} + +impl MappedStructure for Context { + fn get_value(&self, id: &Identifier) -> Option { + if id.len() == 0 { + return None; + } + let mut cur_val : Option<&Value> = if let IdentifierValue::Name(s) = &id[0] { + self.get(s) + } else { + return None; + }; + if cur_val.is_none() { + return None; + } + + for idx in 1..id.len() { + if let IdentifierValue::Name(s) = &id[idx] && + let Some(Value::Dictionary(d)) = &cur_val { + cur_val.replace( if let Some(v) = d.get(s) { + v + } else { + return None; + }); + } + } + + return cur_val.cloned(); + } } impl Crimtag { @@ -157,7 +200,7 @@ impl Crimtag { } } - pub fn render_partial(&self, view: &str, input: &Value ) -> Result { + pub fn render_partial(&self, view: &str, input: &impl MappedStructure ) -> Result { if let Some(view) = self.views.get(view) { return view.process( input ) } else { @@ -165,14 +208,13 @@ impl Crimtag { } } - pub fn render(&self, view: &str, input: &Value ) -> Result { + pub fn render(&self, view: &str, input: &impl MappedStructure ) -> Result { if let Some(view) = self.views.get( view ) { - let output = view.process( input )?; + let mut output = view.process( input )?; if let Some(l) = &view.layout { self.render( l, &output ) } else { - if let Value::Dictionary(mut d) = output && - let Some(content) = d.remove("content") && + if let Some(content) = output.remove("content") && let Value::String(s) = content { Ok(s) @@ -187,38 +229,40 @@ impl Crimtag { } } -fn exec(input: &Value, tokens: &Vec, buf: &mut String) -> Result<(),ParseError> { +fn exec(input: &impl MappedStructure, tokens: &Vec, buf: &mut String) -> Result<(),ParseError> { for token in tokens { match token { - Token::Show(s,p) => { + Token::Show(ident,p) => { let format = if let Some(s) = p.get("format") { s } else { &"".to_string() }; - if let Value::Dictionary(d) = input && - let Some(value) = d.get(s) { - match value { - Value::Dictionary(_) => { - buf.push_str(""); - } - Value::List(_) => { - buf.push_str(""); - } - Value::String(s) => { - buf.push_str(s); - } - Value::Int(i) => { - buf.push_str(&i.to_string()); - } - Value::Float(f) => { - if format == "" { - buf.push_str(&f.to_string()); - } else { - buf.push_str(&f.to_string()); - } + if let Some(value) = input.get_value( &ident ) { + match value { + Value::Dictionary(_) => { + buf.push_str(""); + } + Value::List(_) => { + buf.push_str(""); + } + Value::String(s) => { + buf.push_str(s.as_str()); + } + Value::Int(i) => { + buf.push_str(&i.to_string()); + } + Value::Float(f) => { + if format == "" { + buf.push_str(&f.to_string()); + } else { + buf.push_str(&f.to_string()); } } + Value::Identifier(_) => { + // ??? + } + } } } Token::Text(s) => { @@ -254,14 +298,14 @@ mod tests { let mut ct = Crimtag::new(); if let Err(e) = ct.load_static(r#"Here is a sample [|view "index" theme="standard"|>Hi<|view|] -That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body [|show "content"|] and end<|view|] aoeu +That was fun! now a placeholder: [|view "placeholder"|] and now one with an implicit [|view "simple"|>Body -> [|show content|] <- and end<|view|] aoeu -[|view "index" layout="simple"|>Whatever man!<|view|] +[|view "person" layout="simple"|>Welcome [|show person.last_name|], [|show person.first_name|]! It's lovely to see you again.<|view|] Now with explicit outputs: [|view "complex"|> [|output "content"|> - Here's a [|show "name"|]. + Here's a [|show name|]. <|output|] [|output "sidebar"|> What's up world? @@ -274,13 +318,15 @@ Now with explicit outputs: } else { println!("It finished"); if let Ok(v) = ct.render( - "index", - &Value::Dictionary( - HashMap::from([ - ("hi".to_string(),Value::String("hi".to_string())) - ]) - ) - ) { + "person", + &Context::from([ + ("hi".to_string(),Value::String("hi".to_string())), + ("person".to_string(),Value::Dictionary(HashMap::from([ + ("first_name".to_string(), Value::String("Bob".to_string())), + ("last_name".to_string(), Value::String("Smith".to_string())), + ]))), + ]) + ) { println!("View result: {:?}", v ); } else { println!("Error?"); -- cgit v1.2.3