diff --git a/src/app.rs b/src/app.rs index 4196a39..c179ab7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -31,6 +31,7 @@ pub enum EwwCommand { }, KillServer, PrintState(crossbeam_channel::Sender), + PrintDebug(crossbeam_channel::Sender), } #[derive(Debug, Clone, PartialEq)] @@ -73,6 +74,10 @@ impl App { .join("\n"); sender.send(output).context("sending response from main thread") } + EwwCommand::PrintDebug(sender) => { + let output = format!("state: {:#?}\n\nconfig: {:#?}", &self.eww_state, &self.eww_config); + sender.send(output).context("sending response from main thread") + } }; util::print_result_err("while handling event", &result); diff --git a/src/config/element.rs b/src/config/element.rs index 78a3dab..cbce72f 100644 --- a/src/config/element.rs +++ b/src/config/element.rs @@ -4,7 +4,7 @@ use regex::Regex; use std::ops::Range; use crate::{ - value::{AttrValue, VarName}, + value::{AttrName, AttrValue}, with_text_pos_context, }; use maplit::hashmap; @@ -44,7 +44,7 @@ impl WidgetDefinition { pub struct WidgetUse { pub name: String, pub children: Vec, - pub attrs: HashMap, + pub attrs: HashMap, pub text_pos: Option, } @@ -75,9 +75,6 @@ impl WidgetUse { }; let text_pos = xml.text_pos(); let widget_use = 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()) => WidgetUse::from_text_with_var_refs(&text.text()), XmlNode::Text(text) => WidgetUse::simple_text(AttrValue::parse_string(text.text())), XmlNode::Element(elem) => WidgetUse { name: elem.tag_name().to_owned(), @@ -85,7 +82,12 @@ impl WidgetUse { attrs: elem .attributes() .iter() - .map(|attr| (attr.name().to_owned(), AttrValue::parse_string(attr.value().to_owned()))) + .map(|attr| { + ( + AttrName(attr.name().to_owned()), + AttrValue::parse_string(attr.value().to_owned()), + ) + }) .collect::>(), ..WidgetUse::default() }, @@ -98,19 +100,7 @@ impl WidgetUse { WidgetUse { name: "label".to_owned(), children: vec![], - attrs: hashmap! { "text".to_owned() => text }, // TODO this hardcoded "text" is dumdum - ..WidgetUse::default() - } - } - - pub fn from_text_with_var_refs(text: &str) -> Self { - WidgetUse { - name: "text".to_owned(), - children: parse_string_with_var_refs(text) - .into_iter() - .map(StringOrVarRef::to_attr_value) - .map(WidgetUse::simple_text) - .collect(), + attrs: hashmap! { AttrName("text".to_owned()) => text }, // TODO this hardcoded "text" is dumdum ..WidgetUse::default() } } @@ -127,72 +117,12 @@ 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::from_string(x)), - StringOrVarRef::VarRef(x) => AttrValue::VarRef(VarName(x)), - } - } -} - -// TODO this could be a fancy Iterator implementation, ig -fn parse_string_with_var_refs(s: &str) -> Vec { - let mut elements = Vec::new(); - - let mut cur_word = "".to_owned(); - let mut cur_varref: Option = 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)] mod test { use super::*; use maplit::hashmap; use pretty_assertions::assert_eq; - fn mk_attr_str(s: &str) -> AttrValue { - AttrValue::Concrete(PrimitiveValue::from_string(s.to_owned())) - } - #[test] fn test_simple_text() { let expected_attr_value = AttrValue::Concrete(PrimitiveValue::from_string("my text".to_owned())); @@ -202,31 +132,12 @@ mod test { WidgetUse { name: "label".to_owned(), children: Vec::new(), - attrs: hashmap! { "text".to_owned() => expected_attr_value}, + attrs: hashmap! { AttrName("text".to_owned()) => expected_attr_value}, ..WidgetUse::default() }, ) } - #[test] - fn test_text_with_var_refs() { - let expected_attr_value1 = mk_attr_str("my text"); - let expected_attr_value2 = AttrValue::VarRef(VarName("var".to_owned())); - let widget = WidgetUse::from_text_with_var_refs("my text{{var}}"); - assert_eq!( - widget, - WidgetUse { - name: "layout".to_owned(), - attrs: hashmap! { "halign".to_owned() => mk_attr_str("center"), "space-evenly".to_owned() => mk_attr_str("false")}, - children: vec![ - WidgetUse::simple_text(expected_attr_value1), - WidgetUse::simple_text(expected_attr_value2), - ], - ..WidgetUse::default() - } - ) - } - #[test] fn test_parse_widget_use() { let input = r#" @@ -238,14 +149,11 @@ mod test { let document = roxmltree::Document::parse(input).unwrap(); let xml = XmlNode::from(document.root_element().clone()); - println!("{}", xml); - assert_eq!(true, false); - let expected = WidgetUse { name: "widget_name".to_owned(), attrs: hashmap! { - "attr1".to_owned() => AttrValue::Concrete(PrimitiveValue::from_string("hi".to_owned())), - "attr2".to_owned() => AttrValue::Concrete(PrimitiveValue::from_string("12".to_owned())), + AttrName("attr1".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("hi".to_owned())), + AttrName("attr2".to_owned()) => AttrValue::Concrete(PrimitiveValue::from_string("12".to_owned())), }, children: vec![ WidgetUse::new("child_widget".to_owned(), Vec::new()), @@ -284,21 +192,4 @@ mod test { WidgetDefinition::from_xml_element(xml.as_element().unwrap().to_owned()).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()), - ], - ) - } } diff --git a/src/eww_state.rs b/src/eww_state.rs index d876553..6c91323 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -1,5 +1,9 @@ -use crate::{config::WindowName, value::VarName}; +use crate::{ + config::WindowName, + value::{self, AttrName, VarName}, +}; use anyhow::*; +use itertools::Itertools; use std::{collections::HashMap, process::Command, sync::Arc}; use crate::value::{AttrValue, PrimitiveValue}; @@ -7,9 +11,10 @@ use crate::value::{AttrValue, PrimitiveValue}; /// Handler that get's executed to apply the necessary parts of the eww state to /// a gtk widget. These are created and initialized in EwwState::resolve. pub struct StateChangeHandler { - func: Box) -> Result<()> + 'static>, - constant_values: HashMap, - unresolved_attrs: HashMap, + func: Box) -> Result<()> + 'static>, + constant_values: HashMap, + unresolved_attrs: HashMap, + string_with_varrefs_resolvers: HashMap) -> PrimitiveValue>>, } impl StateChangeHandler { @@ -24,6 +29,9 @@ impl StateChangeHandler { .with_context(|| format!("Unknown variable '{}' was referenced", var_ref))?; 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); if let Err(err) = result { @@ -117,21 +125,35 @@ impl EwwState { &'a self, local_env: &'a HashMap, value: &'a AttrValue, - ) -> Result<&'a PrimitiveValue> { + ) -> Result { match value { - AttrValue::Concrete(primitive) => Ok(&primitive), + AttrValue::Concrete(primitive) => Ok(primitive.clone()), AttrValue::VarRef(var_name) => match local_env.get(var_name) { // look up if variables are found in the local env, and resolve as far as possible - Some(AttrValue::Concrete(primitive)) => Ok(primitive), + Some(AttrValue::Concrete(primitive)) => Ok(primitive.clone()), Some(AttrValue::VarRef(var_name)) => self .variables_state .get(var_name) + .cloned() .ok_or_else(|| anyhow!("Unknown variable '{}' referenced", var_name)), + Some(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), 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), } } @@ -140,11 +162,11 @@ impl EwwState { /// nesting var_refs from local-env. This means that no elements in the /// local_env may be var-refs into the local_env again, but only into the /// global state. - pub fn resolve) -> Result<()> + 'static + Clone>( + pub fn resolve) -> Result<()> + 'static + Clone>( &mut self, window_name: &WindowName, local_env: &HashMap, - mut needed_attributes: HashMap, + mut needed_attributes: HashMap, set_value: F, ) { // Resolve first collects all variable references and creates a set of @@ -159,8 +181,11 @@ impl EwwState { .entry(window_name.clone()) .or_insert_with(EwwWindowState::default); + let mut string_with_varrefs_resolvers: HashMap<_, Box) -> PrimitiveValue>> = + HashMap::new(); + let mut resolved_attrs = HashMap::new(); - let mut unresolved_attrs: HashMap = HashMap::new(); + let mut unresolved_attrs: HashMap = HashMap::new(); needed_attributes .drain() .for_each(|(attr_name, attr_value)| match attr_value { @@ -168,8 +193,19 @@ impl EwwState { 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()); @@ -185,12 +221,13 @@ impl EwwState { }, }); - if unresolved_attrs.is_empty() { + 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 { + string_with_varrefs_resolvers, func: Box::new(set_value.clone()), constant_values: resolved_attrs, unresolved_attrs, @@ -205,6 +242,26 @@ impl EwwState { } } +pub fn generate_string_with_var_refs_resolver( + string_with_varrefs: value::StringWithVarRefs, +) -> impl Fn(&HashMap) -> PrimitiveValue { + move |variables: &HashMap| { + 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(""), + ) + } +} + /// Run a command and get the output pub fn run_command(cmd: &str) -> Result { let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?; diff --git a/src/main.rs b/src/main.rs index 79f5700..9af895c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ extern crate gio; extern crate gtk; +use crate::value::{PrimitiveValue, VarName}; use anyhow::*; use eww_state::*; use log; @@ -19,7 +20,6 @@ use std::{ path::{Path, PathBuf}, }; use structopt::StructOpt; -use value::{PrimitiveValue, VarName}; pub mod app; pub mod config; @@ -103,6 +103,9 @@ pub enum OptAction { #[structopt(name = "state", help = "Print the current eww-state")] ShowState, + + #[structopt(name = "debug", help = "Print out the widget structure as seen by eww")] + ShowDebug, } impl OptAction { @@ -116,6 +119,10 @@ impl OptAction { let (send, recv) = crossbeam_channel::unbounded(); (app::EwwCommand::PrintState(send), Some(recv)) } + OptAction::ShowDebug => { + let (send, recv) = crossbeam_channel::unbounded(); + (app::EwwCommand::PrintDebug(send), Some(recv)) + } } } } diff --git a/src/value.rs b/src/value.rs deleted file mode 100644 index 444aa57..0000000 --- a/src/value.rs +++ /dev/null @@ -1,176 +0,0 @@ -use anyhow::*; -use derive_more; -use lazy_static::lazy_static; -use ref_cast::RefCast; -use regex::Regex; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt}; - -use crate::impl_try_from; - -#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)] -pub struct PrimitiveValue(String); - -impl fmt::Display for PrimitiveValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\"", self.0) - } -} -impl fmt::Debug for PrimitiveValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl std::str::FromStr for PrimitiveValue { - type Err = anyhow::Error; - - /// parses the value, trying to turn it into a number and a boolean first, - /// before deciding that it is a string. - fn from_str(s: &str) -> Result { - Ok(PrimitiveValue::from_string(s.to_string())) - } -} - -impl_try_from!(PrimitiveValue { - for String => |x| x.as_string(); - for f64 => |x| x.as_f64(); - for bool => |x| x.as_bool(); -}); - -impl From for PrimitiveValue { - fn from(x: bool) -> Self { - PrimitiveValue(format!("{}", x)) - } -} - -impl From for PrimitiveValue { - fn from(s: i32) -> Self { - PrimitiveValue(s.to_string()) - } -} - -impl From<&str> for PrimitiveValue { - fn from(s: &str) -> Self { - PrimitiveValue(s.to_string()) - } -} - -impl PrimitiveValue { - pub fn from_string(s: String) -> Self { - PrimitiveValue(s.to_string()) - } - - /// This will never fail - pub fn as_string(&self) -> Result { - Ok(self.0.to_owned()) - } - - pub fn as_f64(&self) -> Result { - self.0 - .parse() - .map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e)) - } - - pub fn as_i32(&self) -> Result { - self.0 - .parse() - .map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e)) - } - - pub fn as_bool(&self) -> Result { - self.0 - .parse() - .map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e)) - } -} - -#[repr(transparent)] -#[derive( - Debug, - Clone, - Hash, - PartialEq, - Eq, - derive_more::AsRef, - derive_more::From, - derive_more::FromStr, - Serialize, - Deserialize, - RefCast, -)] -pub struct VarName(pub String); - -impl std::borrow::Borrow for VarName { - fn borrow(&self) -> &str { - &self.0 - } -} - -impl fmt::Display for VarName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum AttrValue { - Concrete(PrimitiveValue), - VarRef(VarName), -} - -impl AttrValue { - pub fn as_string(&self) -> Result { - match self { - AttrValue::Concrete(x) => x.as_string(), - _ => Err(anyhow!("{:?} is not a string", self)), - } - } - - pub fn as_f64(&self) -> Result { - match self { - AttrValue::Concrete(x) => x.as_f64(), - _ => Err(anyhow!("{:?} is not an f64", self)), - } - } - - pub fn as_i32(&self) -> Result { - match self { - AttrValue::Concrete(x) => x.as_i32(), - _ => Err(anyhow!("{:?} is not an i32", self)), - } - } - - pub fn as_bool(&self) -> Result { - match self { - AttrValue::Concrete(x) => x.as_bool(), - _ => Err(anyhow!("{:?} is not a bool", self)), - } - } - - pub fn as_var_ref(&self) -> Result<&VarName> { - match self { - AttrValue::VarRef(x) => Ok(x), - _ => Err(anyhow!("{:?} is not a variable reference", self)), - } - } - - /// parses the value, trying to turn it into VarRef, - /// a number and a boolean first, before deciding that it is a string. - pub fn parse_string(s: String) -> Self { - lazy_static! { - 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(VarName(ref_name.to_owned())) - } else { - AttrValue::Concrete(PrimitiveValue::from_string(s)) - } - } -} -impl From for AttrValue { - fn from(value: PrimitiveValue) -> Self { - AttrValue::Concrete(value) - } -} diff --git a/src/value/attr_value.rs b/src/value/attr_value.rs new file mode 100644 index 0000000..889c941 --- /dev/null +++ b/src/value/attr_value.rs @@ -0,0 +1,108 @@ +use anyhow::*; +use lazy_static::lazy_static; +use regex::Regex; + +use super::*; + +#[derive(Clone, Debug, PartialEq)] +pub enum AttrValue { + Concrete(PrimitiveValue), + StringWithVarRefs(StringWithVarRefs), + VarRef(VarName), +} + +impl AttrValue { + pub fn as_string(&self) -> Result { + match self { + AttrValue::Concrete(x) => x.as_string(), + _ => Err(anyhow!("{:?} is not a string", self)), + } + } + + pub fn as_f64(&self) -> Result { + match self { + AttrValue::Concrete(x) => x.as_f64(), + _ => Err(anyhow!("{:?} is not an f64", self)), + } + } + + pub fn as_i32(&self) -> Result { + match self { + AttrValue::Concrete(x) => x.as_i32(), + _ => Err(anyhow!("{:?} is not an i32", self)), + } + } + + pub fn as_bool(&self) -> Result { + match self { + AttrValue::Concrete(x) => x.as_bool(), + _ => Err(anyhow!("{:?} is not a bool", self)), + } + } + + pub fn as_var_ref(&self) -> Result<&VarName> { + match self { + AttrValue::VarRef(x) => Ok(x), + _ => Err(anyhow!("{:?} is not a variable reference", self)), + } + } + + /// parses the value, trying to turn it into VarRef, + /// a number and a boolean first, before deciding that it is a string. + pub fn parse_string(s: String) -> Self { + lazy_static! { + static ref VAR_REF_PATTERN: Regex = Regex::new("\\{\\{(.*?)\\}\\}").unwrap(); + }; + + let pattern: &Regex = &*VAR_REF_PATTERN; + if let Some(match_range) = pattern.find(&s) { + if match_range.start() == 0 && match_range.end() == s.len() { + // we can unwrap here, as we just verified that there is a valid match already + let ref_name = VAR_REF_PATTERN.captures(&s).and_then(|cap| cap.get(1)).unwrap().as_str(); + AttrValue::VarRef(VarName(ref_name.to_owned())) + } else { + AttrValue::StringWithVarRefs(StringWithVarRefs::parse_string(&s)) + } + } else { + AttrValue::Concrete(PrimitiveValue::from_string(s)) + } + } +} +impl From for AttrValue { + fn from(value: PrimitiveValue) -> Self { + AttrValue::Concrete(value) + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + #[test] + fn test_parse_concrete_attr_value() { + assert_eq!( + AttrValue::Concrete(PrimitiveValue::from_string("foo".to_owned())), + AttrValue::parse_string("foo".to_owned()) + ); + } + #[test] + fn test_parse_var_ref_attr_value() { + assert_eq!( + AttrValue::VarRef(VarName("foo".to_owned())), + AttrValue::parse_string("{{foo}}".to_owned()) + ); + } + #[test] + fn test_parse_string_with_var_refs_attr_value() { + assert_eq!( + AttrValue::StringWithVarRefs( + vec![ + StringOrVarRef::VarRef(VarName("var".to_owned())), + StringOrVarRef::primitive("something".to_owned()) + ] + .into() + ), + AttrValue::parse_string("{{var}}something".to_owned()) + ); + } +} diff --git a/src/value/mod.rs b/src/value/mod.rs new file mode 100644 index 0000000..e2b7e45 --- /dev/null +++ b/src/value/mod.rs @@ -0,0 +1,59 @@ +use derive_more; +use ref_cast::RefCast; +use serde::{Deserialize, Serialize}; +use std::fmt; + +pub mod attr_value; +pub mod primitive; +pub mod string_with_varrefs; +pub use attr_value::*; +pub use primitive::*; +pub use string_with_varrefs::*; + +#[repr(transparent)] +#[derive( + Clone, Hash, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::FromStr, Serialize, Deserialize, RefCast, +)] +pub struct VarName(pub String); + +impl std::borrow::Borrow for VarName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for VarName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Debug for VarName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "VarName(\"{}\")", self.0) + } +} + +#[repr(transparent)] +#[derive( + Clone, Hash, PartialEq, Eq, derive_more::AsRef, derive_more::From, derive_more::FromStr, Serialize, Deserialize, RefCast, +)] +pub struct AttrName(pub String); + +impl std::borrow::Borrow for AttrName { + fn borrow(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for AttrName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Debug for AttrName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AttrName({})", self.0) + } +} diff --git a/src/value/primitive.rs b/src/value/primitive.rs new file mode 100644 index 0000000..9c05f8a --- /dev/null +++ b/src/value/primitive.rs @@ -0,0 +1,87 @@ +use anyhow::*; +use derive_more; +use serde::{Deserialize, Serialize}; +use std::{convert::TryFrom, fmt}; + +use crate::impl_try_from; + +#[derive(Clone, PartialEq, Deserialize, Serialize, derive_more::From)] +pub struct PrimitiveValue(String); + +impl fmt::Display for PrimitiveValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl fmt::Debug for PrimitiveValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "\"{}\"", self) + } +} + +impl std::str::FromStr for PrimitiveValue { + type Err = anyhow::Error; + + /// parses the value, trying to turn it into a number and a boolean first, + /// before deciding that it is a string. + fn from_str(s: &str) -> Result { + Ok(PrimitiveValue::from_string(s.to_string())) + } +} + +impl_try_from!(PrimitiveValue { + for String => |x| x.as_string(); + for f64 => |x| x.as_f64(); + for bool => |x| x.as_bool(); +}); + +impl From for PrimitiveValue { + fn from(x: bool) -> Self { + PrimitiveValue(x.to_string()) + } +} + +impl From for PrimitiveValue { + fn from(s: i32) -> Self { + PrimitiveValue(s.to_string()) + } +} + +impl From<&str> for PrimitiveValue { + fn from(s: &str) -> Self { + PrimitiveValue(s.to_string()) + } +} + +impl PrimitiveValue { + pub fn from_string(s: String) -> Self { + PrimitiveValue(s.to_string()) + } + + pub fn into_inner(self) -> String { + self.0 + } + + /// This will never fail + pub fn as_string(&self) -> Result { + Ok(self.0.to_owned()) + } + + pub fn as_f64(&self) -> Result { + self.0 + .parse() + .map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e)) + } + + pub fn as_i32(&self) -> Result { + self.0 + .parse() + .map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e)) + } + + pub fn as_bool(&self) -> Result { + self.0 + .parse() + .map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e)) + } +} diff --git a/src/value/string_with_varrefs.rs b/src/value/string_with_varrefs.rs new file mode 100644 index 0000000..3a6beaa --- /dev/null +++ b/src/value/string_with_varrefs.rs @@ -0,0 +1,135 @@ +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); + +impl IntoIterator for StringWithVarRefs { + type IntoIter = std::vec::IntoIter; + type Item = StringOrVarRef; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl FromIterator for StringWithVarRefs { + fn from_iter>(iter: T) -> Self { + let mut result = StringWithVarRefs(Vec::new()); + result.0.extend(iter); + result + } +} + +impl StringWithVarRefs { + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + + pub fn var_refs(&self) -> impl Iterator { + self.0.iter().filter_map(|x| x.as_var_ref()) + } + + pub fn resolve_one_level(self, variables: &HashMap) -> 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 = 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()), + ], + ) + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 72646bf..532fb26 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,10 +1,11 @@ use crate::{ config::{element, WindowName}, eww_state::*, - value::{AttrValue, VarName}, + value::{AttrName, AttrValue, VarName}, }; use anyhow::*; use gtk::prelude::*; +use itertools::Itertools; use std::{collections::HashMap, process::Command}; use widget_definitions::*; @@ -26,7 +27,7 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> { eww_state: &'a mut EwwState, local_env: &'b HashMap, widget: &'c element::WidgetUse, - unhandled_attrs: Vec<&'c str>, + unhandled_attrs: Vec<&'c AttrName>, window_name: &'d WindowName, widget_definitions: &'e HashMap, } @@ -72,11 +73,14 @@ pub fn widget_use_to_gtk_widget( .into_iter() .map(|(attr_name, attr_value)| { ( - VarName(attr_name), + 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), }, ) @@ -120,7 +124,7 @@ fn build_builtin_gtk_widget( local_env, widget, window_name, - unhandled_attrs: widget.attrs.keys().map(|x| x.as_ref()).collect(), + unhandled_attrs: widget.attrs.keys().collect(), widget_definitions, }; let gtk_widget = match widget_to_gtk_widget(&mut bargs) { @@ -164,9 +168,9 @@ fn build_builtin_gtk_widget( if !bargs.unhandled_attrs.is_empty() { eprintln!( "{}WARN: Unknown attribute used in {}: {}", - widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(), + widget.text_pos.map(|x| format!("{} | ", x)).unwrap_or_default(), widget.name, - bargs.unhandled_attrs.join(", ") + bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ") ) } @@ -182,13 +186,14 @@ macro_rules! resolve_block { }) => { $({ $( - $args.unhandled_attrs.retain(|a| a != &::std::stringify!($attr_name).replace('_', "-")); + $args.unhandled_attrs.retain(|a| &a.0 != &::std::stringify!($attr_name).replace('_', "-")); )* let attr_map: Result<_> = try { ::maplit::hashmap! { $( - ::std::stringify!($attr_name).to_owned() => resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?) + crate::value::AttrName(::std::stringify!($attr_name).to_owned()) => + resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?) ),* } }; diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index 02e253b..ef7c85a 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -240,7 +240,7 @@ fn build_gtk_label(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Label::new(None); resolve_block!(bargs, gtk_widget, { // @prop - the text to display - prop(text: as_string) { gtk_widget.set_text(&text) }, + prop(text: as_string) { gtk_widget.set_text(dbg!(&text)) }, }); Ok(gtk_widget) }