Refactor attr_value to only have one variant
This commit is contained in:
parent
b6f28990ce
commit
b06162dcf5
8 changed files with 196 additions and 382 deletions
|
@ -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()
|
||||||
},
|
},
|
||||||
|
|
181
src/eww_state.rs
181
src/eww_state.rs
|
@ -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(""),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,) => {
|
||||||
|
|
|
@ -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};
|
||||||
|
|
Loading…
Add table
Reference in a new issue