Implement inline var-refs

This commit is contained in:
elkowar 2020-10-04 22:47:32 +02:00
parent bb60ab69c3
commit f92f8bfb66
5 changed files with 104 additions and 27 deletions

1
Cargo.lock generated
View file

@ -310,6 +310,7 @@ dependencies = [
"hotwatch", "hotwatch",
"ipc-channel", "ipc-channel",
"itertools", "itertools",
"lazy_static",
"log 0.4.11", "log 0.4.11",
"maplit", "maplit",
"notify", "notify",

View file

@ -33,6 +33,7 @@ scheduled-executor = "0.4"
debug_stub_derive = "0.3" debug_stub_derive = "0.3"
log = "0.4" log = "0.4"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
lazy_static = "1.4.0"
#thiserror = "1.0" #thiserror = "1.0"

View file

@ -1,4 +1,6 @@
use super::*; use super::*;
use lazy_static::lazy_static;
use regex::Regex;
use crate::value::AttrValue; use crate::value::AttrValue;
use crate::with_text_pos_context; use crate::with_text_pos_context;
@ -52,7 +54,13 @@ impl WidgetUse {
} }
} }
pub fn from_xml_node(xml: XmlNode) -> Result<Self> { pub fn from_xml_node(xml: XmlNode) -> Result<Self> {
lazy_static! {
static ref PATTERN: Regex = Regex::new("\\{\\{(.*)\\}\\}").unwrap();
};
match xml { match xml {
// TODO the matching here is stupid. This currently uses the inefficient function to parse simple single varrefs,
// TODO and does the regex match twice in the from_text_with_var_refs part
XmlNode::Text(text) if PATTERN.is_match(&text.text()) => Ok(WidgetUse::from_text_with_var_refs(&text.text())),
XmlNode::Text(text) => Ok(WidgetUse::simple_text(AttrValue::parse_string(text.text()))), XmlNode::Text(text) => Ok(WidgetUse::simple_text(AttrValue::parse_string(text.text()))),
XmlNode::Element(elem) => Ok(WidgetUse { XmlNode::Element(elem) => Ok(WidgetUse {
name: elem.tag_name().to_string(), name: elem.tag_name().to_string(),
@ -75,24 +83,19 @@ impl WidgetUse {
} }
} }
// TODO Even just thinking of this gives me horrible nightmares..... pub fn from_text_with_var_refs(text: &str) -> Self {
//pub fn from_text(text: String) -> Self { WidgetUse {
//WidgetUse::text_with_var_refs(
//text.split(" ")
//.map(|word| AttrValue::parse_string(word.to_owned()))
//.collect_vec(),
//)
//}
pub fn text_with_var_refs(elements: Vec<AttrValue>) -> Self {
dbg!(WidgetUse {
name: "layout".to_owned(), name: "layout".to_owned(),
attrs: hashmap! { attrs: hashmap! {
"halign".to_owned() => AttrValue::Concrete(PrimitiveValue::String("center".to_owned())), "halign".to_owned() => AttrValue::Concrete(PrimitiveValue::String("center".to_owned())),
"space-evenly".to_owned() => AttrValue::Concrete(PrimitiveValue::String("false".to_owned())), "space-evenly".to_owned() => AttrValue::Concrete(PrimitiveValue::String("false".to_owned())),
}, },
children: elements.into_iter().map(WidgetUse::simple_text).collect(), children: parse_string_with_var_refs(text)
}) .into_iter()
.map(StringOrVarRef::to_attr_value)
.map(WidgetUse::simple_text)
.collect(),
}
} }
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> { pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
@ -102,6 +105,62 @@ impl WidgetUse {
} }
} }
#[derive(Clone, Debug, PartialEq)]
enum StringOrVarRef {
String(String),
VarRef(String),
}
impl StringOrVarRef {
fn to_attr_value(self) -> AttrValue {
match self {
StringOrVarRef::String(x) => AttrValue::Concrete(PrimitiveValue::parse_string(&x)),
StringOrVarRef::VarRef(x) => AttrValue::VarRef(x),
}
}
}
// TODO this could be a fancy Iterator implementation, ig
fn parse_string_with_var_refs(s: &str) -> Vec<StringOrVarRef> {
let mut elements = Vec::new();
let mut cur_word = "".to_owned();
let mut cur_varref: Option<String> = None;
let mut curly_count = 0;
for c in s.chars() {
if let Some(ref mut varref) = cur_varref {
if c == '}' {
curly_count -= 1;
if curly_count == 0 {
elements.push(StringOrVarRef::VarRef(std::mem::take(varref)));
cur_varref = None
}
} else {
curly_count = 2;
varref.push(c);
}
} else {
if c == '{' {
curly_count += 1;
if curly_count == 2 {
if !cur_word.is_empty() {
elements.push(StringOrVarRef::String(std::mem::take(&mut cur_word)));
}
cur_varref = Some(String::new())
}
} else {
cur_word.push(c);
}
}
}
if let Some(unfinished_varref) = cur_varref.take() {
elements.push(StringOrVarRef::String(unfinished_varref));
} else if !cur_word.is_empty() {
elements.push(StringOrVarRef::String(cur_word.to_owned()));
}
elements
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -130,7 +189,7 @@ mod test {
fn test_text_with_var_refs() { fn test_text_with_var_refs() {
let expected_attr_value1 = mk_attr_str("my text"); let expected_attr_value1 = mk_attr_str("my text");
let expected_attr_value2 = AttrValue::VarRef("var".to_owned()); let expected_attr_value2 = AttrValue::VarRef("var".to_owned());
let widget = WidgetUse::text_with_var_refs(vec![expected_attr_value1.clone(), expected_attr_value2.clone()]); let widget = WidgetUse::from_text_with_var_refs("my text{{var}}");
assert_eq!( assert_eq!(
widget, widget,
WidgetUse { WidgetUse {
@ -199,4 +258,21 @@ mod test {
WidgetDefinition::from_xml_element(xml.as_element().unwrap()).unwrap() WidgetDefinition::from_xml_element(xml.as_element().unwrap()).unwrap()
); );
} }
#[test]
fn test_parse_string_or_var_ref_list() {
let input = "{{foo}}{{bar}}baz{{bat}}quok{{test}}";
let output = parse_string_with_var_refs(input);
assert_eq!(
output,
vec![
StringOrVarRef::VarRef("foo".to_owned()),
StringOrVarRef::VarRef("bar".to_owned()),
StringOrVarRef::String("baz".to_owned()),
StringOrVarRef::VarRef("bat".to_owned()),
StringOrVarRef::String("quok".to_owned()),
StringOrVarRef::VarRef("test".to_owned()),
],
)
}
} }

View file

@ -1,7 +1,8 @@
use anyhow::*; use anyhow::*;
use derive_more; use derive_more;
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt; use std::fmt;
@ -127,20 +128,18 @@ impl AttrValue {
_ => Err(anyhow!("{:?} is not a bool", self)), _ => Err(anyhow!("{:?} is not a bool", self)),
} }
} }
pub fn as_var_ref(&self) -> Result<&String> {
match self {
AttrValue::VarRef(x) => Ok(x),
_ => Err(anyhow!("{:?} is not a VarRef", self)),
}
}
/// parses the value, trying to turn it into VarRef, /// parses the value, trying to turn it into VarRef,
/// a number and a boolean first, before deciding that it is a string. /// a number and a boolean first, before deciding that it is a string.
pub fn parse_string(s: String) -> Self { pub fn parse_string(s: String) -> Self {
if s.starts_with("$$") { lazy_static! {
AttrValue::VarRef(s.trim_start_matches("$$").to_string()) static ref PATTERN: Regex = Regex::new("^\\{\\{(.*)\\}\\}$").unwrap();
};
if let Some(ref_name) = PATTERN.captures(&s).and_then(|cap| cap.get(1)).map(|x| x.as_str()) {
AttrValue::VarRef(ref_name.to_owned())
} else { } else {
AttrValue::Concrete(PrimitiveValue::parse_string(&s)) AttrValue::Concrete(PrimitiveValue::String(s))
} }
} }
} }

View file

@ -70,7 +70,7 @@ pub fn build_gtk_widget(
let child_widget = element_to_gtk_thing(widget_definitions, bargs.eww_state, local_env, child); let child_widget = element_to_gtk_thing(widget_definitions, bargs.eww_state, local_env, child);
let child_widget = child_widget.with_context(|| { let child_widget = child_widget.with_context(|| {
format!( format!(
"error while building child '{:?}' of '{}'", "error while building child '{:#?}' of '{}'",
&child, &child,
&gtk_widget.get_widget_name() &gtk_widget.get_widget_name()
) )