Refactor attr_value to only have one variant

This commit is contained in:
elkowar 2020-10-18 22:12:07 +02:00
parent b6f28990ce
commit b06162dcf5
8 changed files with 196 additions and 382 deletions

View file

@ -75,19 +75,14 @@ impl WidgetUse {
}; };
let text_pos = xml.text_pos(); let text_pos = xml.text_pos();
let widget_use = match xml { let widget_use = match xml {
XmlNode::Text(text) => WidgetUse::simple_text(AttrValue::parse_string(text.text())), XmlNode::Text(text) => WidgetUse::simple_text(AttrValue::parse_string(&text.text())),
XmlNode::Element(elem) => WidgetUse { XmlNode::Element(elem) => WidgetUse {
name: elem.tag_name().to_owned(), name: elem.tag_name().to_owned(),
children: with_text_pos_context! { elem => elem.children().map(WidgetUse::from_xml_node).collect::<Result<_>>()?}?, children: with_text_pos_context! { elem => elem.children().map(WidgetUse::from_xml_node).collect::<Result<_>>()?}?,
attrs: elem attrs: elem
.attributes() .attributes()
.iter() .iter()
.map(|attr| { .map(|attr| (AttrName(attr.name().to_owned()), AttrValue::parse_string(attr.value())))
(
AttrName(attr.name().to_owned()),
AttrValue::parse_string(attr.value().to_owned()),
)
})
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
..WidgetUse::default() ..WidgetUse::default()
}, },
@ -125,7 +120,7 @@ mod test {
#[test] #[test]
fn test_simple_text() { fn test_simple_text() {
let expected_attr_value = AttrValue::Concrete(PrimitiveValue::from_string("my text".to_owned())); let expected_attr_value = AttrValue::from_primitive("my text");
let widget = WidgetUse::simple_text(expected_attr_value.clone()); let widget = WidgetUse::simple_text(expected_attr_value.clone());
assert_eq!( assert_eq!(
widget, widget,
@ -135,7 +130,7 @@ mod test {
attrs: hashmap! { AttrName("text".to_owned()) => expected_attr_value}, attrs: hashmap! { AttrName("text".to_owned()) => expected_attr_value},
..WidgetUse::default() ..WidgetUse::default()
}, },
) );
} }
#[test] #[test]
@ -152,12 +147,12 @@ mod test {
let expected = WidgetUse { let expected = WidgetUse {
name: "widget_name".to_owned(), name: "widget_name".to_owned(),
attrs: hashmap! { attrs: hashmap! {
AttrName("attr1".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("hi".to_owned())), AttrName("attr1".to_owned()) => AttrValue::from_primitive("hi"),
AttrName("attr2".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("12".to_owned())), AttrName("attr2".to_owned()) => AttrValue::from_primitive("12"),
}, },
children: vec![ children: vec![
WidgetUse::new("child_widget".to_owned(), Vec::new()), WidgetUse::new("child_widget".to_owned(), Vec::new()),
WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::from_string("foo".to_owned()))), WidgetUse::simple_text(AttrValue::from_primitive("foo".to_owned())),
], ],
..WidgetUse::default() ..WidgetUse::default()
}; };
@ -179,9 +174,7 @@ mod test {
size: Some((12, 20)), size: Some((12, 20)),
structure: WidgetUse { structure: WidgetUse {
name: "layout".to_owned(), name: "layout".to_owned(),
children: vec![WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::from_string( children: vec![WidgetUse::simple_text(AttrValue::from_primitive("test"))],
"test".to_owned(),
)))],
attrs: HashMap::new(), attrs: HashMap::new(),
..WidgetUse::default() ..WidgetUse::default()
}, },

View file

@ -1,9 +1,9 @@
use crate::{ use crate::{
config::WindowName, config::WindowName,
value::{self, AttrName, VarName}, util,
value::{AttrName, StringOrVarRef, VarName},
}; };
use anyhow::*; use anyhow::*;
use itertools::Itertools;
use std::{collections::HashMap, process::Command, sync::Arc}; use std::{collections::HashMap, process::Command, sync::Arc};
use crate::value::{AttrValue, PrimitiveValue}; use crate::value::{AttrValue, PrimitiveValue};
@ -12,33 +12,33 @@ use crate::value::{AttrValue, PrimitiveValue};
/// a gtk widget. These are created and initialized in EwwState::resolve. /// a gtk widget. These are created and initialized in EwwState::resolve.
pub struct StateChangeHandler { pub struct StateChangeHandler {
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>, func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
constant_values: HashMap<AttrName, PrimitiveValue>, unresolved_values: Vec<(AttrName, AttrValue)>,
unresolved_attrs: HashMap<AttrName, VarName>,
string_with_varrefs_resolvers: HashMap<AttrName, Box<dyn Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue>>,
} }
impl StateChangeHandler { impl StateChangeHandler {
fn used_variables(&self) -> impl Iterator<Item = &VarName> {
self.unresolved_values.iter().flat_map(|(_, value)| value.var_refs())
}
/// Run the StateChangeHandler. /// Run the StateChangeHandler.
/// [`state`] should be the global [EwwState::state]. /// [`state`] should be the global [EwwState::state].
fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) -> Result<()> { fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) {
let mut all_resolved_attrs = self.constant_values.clone(); let resolved_attrs = self
for (attr_name, var_ref) in self.unresolved_attrs.iter() { .unresolved_values
let resolved = state .iter()
.get(var_ref) .cloned()
// TODO provide context here, including line numbers .map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?)))
.with_context(|| format!("Unknown variable '{}' was referenced", var_ref))?; .collect::<Result<_>>();
all_resolved_attrs.insert(attr_name.to_owned(), resolved.clone());
}
for (attr_name, resolver) in self.string_with_varrefs_resolvers.iter() {
all_resolved_attrs.insert(attr_name.to_owned(), resolver(state));
}
let result: Result<_> = (self.func)(all_resolved_attrs); match resolved_attrs {
if let Err(err) = result { Ok(resolved_attrs) => {
eprintln!("WARN: Error while resolving attributes: {}", err); let result: Result<_> = (self.func)(resolved_attrs);
util::print_result_err("while updating UI based after state change", &result);
}
Err(err) => {
eprintln!("Error whiel resolving attributes {:?}", err);
}
} }
Ok(())
} }
} }
@ -54,10 +54,11 @@ impl EwwWindowState {
/// register a new [StateChangeHandler] /// register a new [StateChangeHandler]
fn put_handler(&mut self, handler: StateChangeHandler) { fn put_handler(&mut self, handler: StateChangeHandler) {
let handler = Arc::new(handler); let handler = Arc::new(handler);
for var_name in handler.unresolved_attrs.values() { for var_name in handler.used_variables() {
let entry: &mut Vec<Arc<StateChangeHandler>> = self.state_change_handlers
self.state_change_handlers.entry(var_name.clone()).or_insert_with(Vec::new); .entry(var_name.clone())
entry.push(handler.clone()); .or_insert_with(Vec::new)
.push(handler.clone());
} }
} }
} }
@ -110,9 +111,7 @@ impl EwwState {
.flatten(); .flatten();
for handler in handlers { for handler in handlers {
handler handler.run_with_state(&self.variables_state)
.run_with_state(&self.variables_state)
.with_context(|| format!("When updating value of {}", &key))?;
} }
Ok(()) Ok(())
} }
@ -126,35 +125,18 @@ impl EwwState {
local_env: &'a HashMap<VarName, AttrValue>, local_env: &'a HashMap<VarName, AttrValue>,
value: &'a AttrValue, value: &'a AttrValue,
) -> Result<PrimitiveValue> { ) -> Result<PrimitiveValue> {
match value { value
AttrValue::Concrete(primitive) => Ok(primitive.clone()), .iter()
AttrValue::VarRef(var_name) => match local_env.get(var_name) { .map(|element| match element {
// look up if variables are found in the local env, and resolve as far as possible StringOrVarRef::Primitive(primitive) => Ok(primitive.clone()),
Some(AttrValue::Concrete(primitive)) => Ok(primitive.clone()), StringOrVarRef::VarRef(var_name) => self
Some(AttrValue::VarRef(var_name)) => self
.variables_state .variables_state
.get(var_name) .get(var_name)
.cloned() .cloned()
.ok_or_else(|| anyhow!("Unknown variable '{}' referenced", var_name)), .or_else(|| local_env.get(var_name).and_then(|x| self.resolve_once(local_env, x).ok()))
Some(AttrValue::StringWithVarRefs(content)) => content .with_context(|| format!("Unknown variable '{}' referenced", var_name)),
.iter() })
.map(|x| x.clone().to_attr_value()) .collect()
.map(|value| self.resolve_once(local_env, &value))
.fold_results(String::new(), |acc, cur| format!("{}{}", acc, cur))
.map(PrimitiveValue::from_string),
None => self
.variables_state
.get(var_name)
.cloned()
.ok_or_else(|| anyhow!("Unknown variable '{}' referenced", var_name)),
},
AttrValue::StringWithVarRefs(content) => content
.iter()
.map(|x| x.clone().to_attr_value())
.map(|value| self.resolve_once(local_env, &value))
.fold_results(String::new(), |acc, cur| format!("{}{}", acc, cur))
.map(PrimitiveValue::from_string),
}
} }
/// Resolve takes a function that applies a set of fully resolved attribute /// Resolve takes a function that applies a set of fully resolved attribute
@ -166,99 +148,26 @@ impl EwwState {
&mut self, &mut self,
window_name: &WindowName, window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>, local_env: &HashMap<VarName, AttrValue>,
mut needed_attributes: HashMap<AttrName, AttrValue>, mut attributes: HashMap<AttrName, AttrValue>,
set_value: F, set_value: F,
) { ) {
// Resolve first collects all variable references and creates a set of
// unresolved attribute -> VarName pairs. additionally, all constant values are
// looked up and collected, including the values from the local environment
// These are then used to generate a StateChangeHandler, which is then executed
// and registered in the windows state.
let result: Result<_> = try {
let window_state = self let window_state = self
.windows .windows
.entry(window_name.clone()) .entry(window_name.clone())
.or_insert_with(EwwWindowState::default); .or_insert_with(EwwWindowState::default);
let mut string_with_varrefs_resolvers: HashMap<_, Box<dyn Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue>> = let resolved_attributes: Vec<(AttrName, AttrValue)> = attributes
HashMap::new();
let mut resolved_attrs = HashMap::new();
let mut unresolved_attrs: HashMap<AttrName, VarName> = HashMap::new();
needed_attributes
.drain() .drain()
.for_each(|(attr_name, attr_value)| match attr_value { .map(|(attr_name, attr_value)| (attr_name, attr_value.resolve_one_level(local_env)))
// directly resolve primitive values .collect();
AttrValue::Concrete(primitive) => {
resolved_attrs.insert(attr_name, primitive);
}
AttrValue::StringWithVarRefs(content) => {
let content = content.resolve_one_level(local_env);
let resolver = generate_string_with_var_refs_resolver(content);
string_with_varrefs_resolvers.insert(attr_name, Box::new(resolver));
}
AttrValue::VarRef(var_name) => match local_env.get(&var_name) {
Some(AttrValue::StringWithVarRefs(content)) => {
let content = content.clone().resolve_one_level(local_env);
let resolver = generate_string_with_var_refs_resolver(content);
string_with_varrefs_resolvers.insert(attr_name, Box::new(resolver));
}
// look up if variables are found in the local env, and resolve as far as possible
Some(AttrValue::Concrete(concrete_from_local)) => {
resolved_attrs.insert(attr_name, concrete_from_local.clone());
}
Some(AttrValue::VarRef(var_ref_from_local)) => {
unresolved_attrs.insert(attr_name, var_ref_from_local.clone());
}
None => {
// if it's not in the local env, it must reference the global state,
// and should thus directly be inserted into the unresolved attrs.
unresolved_attrs.insert(attr_name, var_name);
}
},
});
if unresolved_attrs.is_empty() && string_with_varrefs_resolvers.is_empty() {
// if there are no unresolved variables, we can set the value directly
set_value(resolved_attrs)?;
} else {
// otherwise register and execute the handler
let handler = StateChangeHandler { let handler = StateChangeHandler {
string_with_varrefs_resolvers, func: Box::new(set_value),
func: Box::new(set_value.clone()), unresolved_values: resolved_attributes,
constant_values: resolved_attrs,
unresolved_attrs,
}; };
handler.run_with_state(&self.variables_state)?;
window_state.put_handler(handler);
}
};
if let Err(e) = result {
eprintln!("Error resolving values: {:?}", e);
}
}
}
pub fn generate_string_with_var_refs_resolver( handler.run_with_state(&self.variables_state);
string_with_varrefs: value::StringWithVarRefs, window_state.put_handler(handler);
) -> impl Fn(&HashMap<VarName, PrimitiveValue>) -> PrimitiveValue {
move |variables: &HashMap<VarName, PrimitiveValue>| {
PrimitiveValue::from_string(
string_with_varrefs
.iter()
.map(|entry| match entry {
value::StringOrVarRef::VarRef(var_name) => variables
.get(var_name)
.expect(&format!("Impossible state: unknown variable {}.\n{:?}", var_name, variables))
.clone()
.into_inner(),
value::StringOrVarRef::Primitive(s) => s.to_string(),
})
.join(""),
)
} }
} }

View file

@ -1,4 +1,5 @@
#![feature(trace_macros)] #![feature(trace_macros)]
#![feature(slice_concat_trait)]
#![feature(result_cloned)] #![feature(result_cloned)]
#![feature(iterator_fold_self)] #![feature(iterator_fold_self)]
#![feature(try_blocks)] #![feature(try_blocks)]
@ -88,10 +89,10 @@ pub enum OptAction {
OpenWindow { OpenWindow {
window_name: config::WindowName, window_name: config::WindowName,
#[structopt(short, long, help="The position of the window, where it should open.")] #[structopt(short, long, help = "The position of the window, where it should open.")]
pos: Option<util::Coords>, pos: Option<util::Coords>,
#[structopt(short, long, help="The size of the window to open")] #[structopt(short, long, help = "The size of the window to open")]
size: Option<util::Coords>, size: Option<util::Coords>,
}, },

View file

@ -1,108 +1,163 @@
use anyhow::*; use anyhow::*;
use lazy_static::lazy_static; use std::{collections::HashMap, iter::FromIterator};
use regex::Regex;
use super::*; use super::*;
#[derive(Clone, Debug, PartialEq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, derive_more::Into, derive_more::From)]
pub enum AttrValue { pub struct AttrValue(Vec<StringOrVarRef>);
Concrete(PrimitiveValue),
StringWithVarRefs(StringWithVarRefs), impl IntoIterator for AttrValue {
VarRef(VarName), type IntoIter = std::vec::IntoIter<Self::Item>;
type Item = StringOrVarRef;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromIterator<StringOrVarRef> for AttrValue {
fn from_iter<T: IntoIterator<Item = StringOrVarRef>>(iter: T) -> Self {
let mut result = AttrValue(Vec::new());
result.0.extend(iter);
result
}
} }
impl AttrValue { impl AttrValue {
pub fn as_string(&self) -> Result<String> { pub fn from_primitive<T: Into<PrimitiveValue>>(v: T) -> Self {
match self { AttrValue(vec![StringOrVarRef::Primitive(v.into())])
AttrValue::Concrete(x) => x.as_string(),
_ => Err(anyhow!("{:?} is not a string", self)),
}
} }
pub fn as_f64(&self) -> Result<f64> { pub fn iter(&self) -> std::slice::Iter<StringOrVarRef> {
match self { self.0.iter()
AttrValue::Concrete(x) => x.as_f64(),
_ => Err(anyhow!("{:?} is not an f64", self)),
}
} }
pub fn as_i32(&self) -> Result<i32> { pub fn var_refs(&self) -> impl Iterator<Item = &VarName> {
match self { self.0.iter().filter_map(|x| x.as_var_ref())
AttrValue::Concrete(x) => x.as_i32(),
_ => Err(anyhow!("{:?} is not an i32", self)),
}
} }
pub fn as_bool(&self) -> Result<bool> { pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> AttrValue {
match self { self.into_iter()
AttrValue::Concrete(x) => x.as_bool(), .flat_map(|entry| match entry {
_ => Err(anyhow!("{:?} is not a bool", self)), StringOrVarRef::VarRef(var_name) => match variables.get(&var_name) {
} Some(value) => value.0.clone(),
_ => vec![StringOrVarRef::VarRef(var_name)],
},
_ => vec![entry],
})
.collect()
} }
pub fn as_var_ref(&self) -> Result<&VarName> { pub fn resolve_fully(self, variables: &HashMap<VarName, PrimitiveValue>) -> Result<PrimitiveValue> {
match self { self.into_iter()
AttrValue::VarRef(x) => Ok(x), .map(|element| match element {
_ => Err(anyhow!("{:?} is not a variable reference", self)), StringOrVarRef::Primitive(x) => Ok(x),
} StringOrVarRef::VarRef(var_name) => variables
.get(&var_name)
.cloned()
.with_context(|| format!("Unknown variable '{}' referenced", var_name)),
})
.collect()
} }
/// parses the value, trying to turn it into VarRef, // TODO this could be a fancy Iterator implementation, ig
/// a number and a boolean first, before deciding that it is a string. pub fn parse_string(s: &str) -> AttrValue {
pub fn parse_string(s: String) -> Self { let mut elements = Vec::new();
lazy_static! {
static ref VAR_REF_PATTERN: Regex = Regex::new("\\{\\{(.*?)\\}\\}").unwrap();
};
let pattern: &Regex = &*VAR_REF_PATTERN; let mut cur_word = "".to_owned();
if let Some(match_range) = pattern.find(&s) { let mut cur_varref: Option<String> = None;
if match_range.start() == 0 && match_range.end() == s.len() { let mut curly_count = 0;
// we can unwrap here, as we just verified that there is a valid match already for c in s.chars() {
let ref_name = VAR_REF_PATTERN.captures(&s).and_then(|cap| cap.get(1)).unwrap().as_str(); if let Some(ref mut varref) = cur_varref {
AttrValue::VarRef(VarName(ref_name.to_owned())) if c == '}' {
} else { curly_count -= 1;
AttrValue::StringWithVarRefs(StringWithVarRefs::parse_string(&s)) if curly_count == 0 {
elements.push(StringOrVarRef::VarRef(VarName(std::mem::take(varref))));
cur_varref = None
} }
} else { } else {
AttrValue::Concrete(PrimitiveValue::from_string(s)) curly_count = 2;
varref.push(c);
}
} else {
if c == '{' {
curly_count += 1;
if curly_count == 2 {
if !cur_word.is_empty() {
elements.push(StringOrVarRef::primitive(std::mem::take(&mut cur_word)));
}
cur_varref = Some(String::new())
}
} else {
cur_word.push(c);
} }
} }
} }
impl From<PrimitiveValue> for AttrValue { if let Some(unfinished_varref) = cur_varref.take() {
fn from(value: PrimitiveValue) -> Self { elements.push(StringOrVarRef::primitive(unfinished_varref));
AttrValue::Concrete(value) } else if !cur_word.is_empty() {
elements.push(StringOrVarRef::primitive(cur_word.to_owned()));
}
AttrValue(elements)
} }
} }
#[cfg(test)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum StringOrVarRef {
Primitive(PrimitiveValue),
VarRef(VarName),
}
impl StringOrVarRef {
pub fn primitive(s: String) -> Self {
StringOrVarRef::Primitive(PrimitiveValue::from_string(s))
}
pub fn as_var_ref(&self) -> Option<&VarName> {
match self {
StringOrVarRef::VarRef(x) => Some(&x),
_ => None,
}
}
pub fn as_primitive(&self) -> Option<&PrimitiveValue> {
match self {
StringOrVarRef::Primitive(x) => Some(&x),
_ => None,
}
}
}
#[cfg(Test)]
mod test { mod test {
use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test] #[test]
fn test_parse_concrete_attr_value() { 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!( assert_eq!(
AttrValue::Concrete(PrimitiveValue::from_string("foo".to_owned())), output,
AttrValue::parse_string("foo".to_owned()) vec![
); StringOrVarRef::VarRef("foo".to_owned()),
} StringOrVarRef::VarRef("bar".to_owned()),
#[test] StringOrVarRef::String("baz".to_owned()),
fn test_parse_var_ref_attr_value() { StringOrVarRef::VarRef("bat".to_owned()),
assert_eq!( StringOrVarRef::String("quok".to_owned()),
AttrValue::VarRef(VarName("foo".to_owned())), StringOrVarRef::VarRef("test".to_owned()),
AttrValue::parse_string("{{foo}}".to_owned()) ],
); )
} }
#[test] #[test]
fn test_parse_string_with_var_refs_attr_value() { fn test_parse_string_with_var_refs_attr_value() {
assert_eq!( assert_eq!(
AttrValue::StringWithVarRefs( AttrValue(
vec![ vec![
StringOrVarRef::VarRef(VarName("var".to_owned())), StringOrVarRef::VarRef(VarName("var".to_owned())),
StringOrVarRef::primitive("something".to_owned()) StringOrVarRef::primitive("something".to_owned())
] ]
.into() .into()
), ),
AttrValue::parse_string("{{var}}something".to_owned()) AttrValue::parse_string("{{var}}something")
); );
} }
} }

View file

@ -1,11 +1,12 @@
use anyhow::*; use anyhow::*;
use derive_more; use derive_more;
use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt}; use std::{convert::TryFrom, fmt, iter::FromIterator};
use crate::impl_try_from; use crate::impl_try_from;
#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)] #[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From, Default)]
pub struct PrimitiveValue(String); pub struct PrimitiveValue(String);
impl fmt::Display for PrimitiveValue { impl fmt::Display for PrimitiveValue {
@ -19,6 +20,12 @@ impl fmt::Debug for PrimitiveValue {
} }
} }
impl FromIterator<PrimitiveValue> for PrimitiveValue {
fn from_iter<T: IntoIterator<Item = PrimitiveValue>>(iter: T) -> Self {
PrimitiveValue(iter.into_iter().join(""))
}
}
impl std::str::FromStr for PrimitiveValue { impl std::str::FromStr for PrimitiveValue {
type Err = anyhow::Error; type Err = anyhow::Error;

View file

@ -1,135 +0,0 @@
use std::{collections::HashMap, iter::FromIterator};
use serde::{Deserialize, Serialize};
use IntoIterator;
use super::{AttrValue, PrimitiveValue, VarName};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, derive_more::Into, derive_more::From)]
pub struct StringWithVarRefs(Vec<StringOrVarRef>);
impl IntoIterator for StringWithVarRefs {
type IntoIter = std::vec::IntoIter<Self::Item>;
type Item = StringOrVarRef;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromIterator<StringOrVarRef> for StringWithVarRefs {
fn from_iter<T: IntoIterator<Item = StringOrVarRef>>(iter: T) -> Self {
let mut result = StringWithVarRefs(Vec::new());
result.0.extend(iter);
result
}
}
impl StringWithVarRefs {
pub fn iter(&self) -> std::slice::Iter<StringOrVarRef> {
self.0.iter()
}
pub fn var_refs(&self) -> impl Iterator<Item = &VarName> {
self.0.iter().filter_map(|x| x.as_var_ref())
}
pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> StringWithVarRefs {
self.into_iter()
.map(|entry| match entry {
StringOrVarRef::VarRef(var_name) => match variables.get(&var_name).clone() {
Some(AttrValue::Concrete(primitive)) => StringOrVarRef::Primitive(primitive.clone()),
_ => StringOrVarRef::VarRef(var_name),
},
_ => entry,
})
.collect()
}
// TODO this could be a fancy Iterator implementation, ig
pub fn parse_string(s: &str) -> StringWithVarRefs {
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(VarName(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::primitive(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::primitive(unfinished_varref));
} else if !cur_word.is_empty() {
elements.push(StringOrVarRef::primitive(cur_word.to_owned()));
}
StringWithVarRefs(elements)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum StringOrVarRef {
Primitive(PrimitiveValue),
VarRef(VarName),
}
impl StringOrVarRef {
pub fn primitive(s: String) -> Self {
StringOrVarRef::Primitive(PrimitiveValue::from_string(s))
}
pub fn to_attr_value(self) -> AttrValue {
match self {
StringOrVarRef::Primitive(x) => AttrValue::Concrete(x),
StringOrVarRef::VarRef(x) => AttrValue::VarRef(x),
}
}
pub fn as_var_ref(&self) -> Option<&VarName> {
match self {
StringOrVarRef::VarRef(x) => Some(&x),
_ => None,
}
}
}
#[cfg(Test)]
mod test {
#[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

@ -71,20 +71,7 @@ pub fn widget_use_to_gtk_widget(
.attrs .attrs
.clone() .clone()
.into_iter() .into_iter()
.map(|(attr_name, attr_value)| { .map(|(attr_name, attr_value)| (VarName(attr_name.0), attr_value.resolve_one_level(local_env)))
(
VarName(attr_name.0),
match attr_value {
AttrValue::VarRef(var_ref) => {
local_env.get(&var_ref).cloned().unwrap_or_else(|| AttrValue::VarRef(var_ref))
}
AttrValue::StringWithVarRefs(content) => {
AttrValue::StringWithVarRefs(content.resolve_one_level(local_env))
}
AttrValue::Concrete(value) => AttrValue::Concrete(value),
},
)
})
.collect(); .collect();
let custom_widget = widget_use_to_gtk_widget( let custom_widget = widget_use_to_gtk_widget(
@ -215,7 +202,7 @@ macro_rules! resolve_block {
}; };
(@get_value $args:ident, $name:expr, = $default:expr) => { (@get_value $args:ident, $name:expr, = $default:expr) => {
$args.widget.get_attr($name).cloned().unwrap_or(AttrValue::Concrete(PrimitiveValue::from($default))) $args.widget.get_attr($name).cloned().unwrap_or(AttrValue::from_primitive($default))
}; };
(@get_value $args:ident, $name:expr,) => { (@get_value $args:ident, $name:expr,) => {

View file

@ -1,8 +1,5 @@
use super::{run_command, BuilderArgs}; use super::{run_command, BuilderArgs};
use crate::{ use crate::{config, eww_state, resolve_block, value::AttrValue};
config, eww_state, resolve_block,
value::{AttrValue, PrimitiveValue},
};
use anyhow::*; use anyhow::*;
use gtk::{prelude::*, ImageExt}; use gtk::{prelude::*, ImageExt};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};