diff --git a/Cargo.lock b/Cargo.lock index 3c19d84..9f9f26e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,9 +119,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" +checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" [[package]] name = "cfg-if" @@ -139,6 +139,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "0.99.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcfabdab475c16a93d669dddfc393027803e347d09663f524447f642fbb84ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "difference" version = "2.0.0" @@ -147,9 +158,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "either" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding" @@ -220,11 +231,13 @@ name = "eww" version = "0.1.0" dependencies = [ "anyhow", + "derive_more", "gdk", "gio", "glib", "gtk", "hocon", + "maplit", "pretty_assertions", "regex", "try_match", @@ -452,14 +465,15 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35993626299fbcaa73c0a19be8fdd01c950f9f3d3ac9cb4fb5532b924ab1a5d7" +checksum = "5e24fb752f8f5d2cf6bbc2c606fd2bc989c81c5e2fe321ab974d54f8b6344eac" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps", + "winapi", ] [[package]] @@ -499,9 +513,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cda4af5c2f4507b7a3535b798dca2135293f4bc3a17f399ce244ef15841c4c" +checksum = "c7e9b997a66e9a23d073f2b1abb4dbfc3925e0b8952f67efd8d9b6e168e4cdc1" dependencies = [ "libc", "system-deps", @@ -618,6 +632,12 @@ version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.3.3" diff --git a/Cargo.toml b/Cargo.toml index d48e4a6..a5fa258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,6 @@ anyhow = "1.0" pretty_assertions = "0.6.1" + +derive_more = "0.99" +maplit = "1" diff --git a/src/config/element.rs b/src/config/element.rs new file mode 100644 index 0000000..9c71d6c --- /dev/null +++ b/src/config/element.rs @@ -0,0 +1,171 @@ +use super::*; + +use hocon_ext::HoconExt; +use std::collections::HashMap; +use std::convert::TryFrom; + +#[derive(Debug, Clone, PartialEq)] +pub struct WidgetDefinition { + pub name: String, + pub structure: ElementUse, + pub size: Option<(i32, i32)>, +} + +impl WidgetDefinition { + pub fn parse_hocon(name: String, hocon: &Hocon) -> Result { + let definition = hocon.as_hash()?; + let structure = definition + .get("structure") + .cloned() + .context("structure must be set in widget definition") + .and_then(ElementUse::parse_hocon)?; + + Ok(WidgetDefinition { + name, + structure, + size: try { + ( + definition.get("size_x")?.as_i64()? as i32, + definition.get("size_y")?.as_i64()? as i32, + ) + }, + }) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ElementUse { + Widget(WidgetUse), + Text(String), +} + +impl ElementUse { + pub fn parse_hocon(hocon: Hocon) -> Result { + match hocon { + Hocon::String(s) => Ok(ElementUse::Text(s)), + Hocon::Hash(hash) => WidgetUse::parse_hocon_hash(hash).map(ElementUse::Widget), + _ => Err(anyhow!("{:?} is not a valid element", hocon)), + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct WidgetUse { + pub name: String, + pub children: Vec, + pub attrs: HashMap, +} + +impl WidgetUse { + pub fn new(name: String, children: Vec) -> Self { + WidgetUse { + name, + children, + attrs: HashMap::new(), + } + } + + pub fn parse_hocon_hash(data: HashMap) -> Result { + let (widget_name, widget_config) = data.into_iter().next().unwrap(); + let widget_config = widget_config.as_hash().unwrap(); + + // TODO allow for `layout_horizontal: [ elements ]` shorthand + + let children = match &widget_config.get("children") { + Some(Hocon::String(text)) => Ok(vec![ElementUse::Text(text.to_string())]), + Some(Hocon::Array(children)) => children + .clone() + .into_iter() + .map(ElementUse::parse_hocon) + .collect::>>(), + None => Ok(Vec::new()), + _ => Err(anyhow!( + "children must be either a list of elements or a string, but was {:?}" + )), + }?; + + let attrs = widget_config + .into_iter() + .filter_map(|(key, value)| Some((key.to_lowercase(), AttrValue::try_from(value).ok()?))) + .collect(); + + Ok(WidgetUse { + name: widget_name.to_string(), + children, + attrs, + }) + } +} + +impl From for ElementUse { + fn from(other: WidgetUse) -> ElementUse { + ElementUse::Widget(other) + } +} + +#[cfg(test)] +mod test { + use super::*; + use maplit::hashmap; + use pretty_assertions::assert_eq; + + #[test] + fn test_parse_text() { + assert_eq!( + ElementUse::parse_hocon(Hocon::String("hi".to_string())).unwrap(), + ElementUse::Text("hi".to_string()) + ); + } + + #[test] + fn test_parse_widget_use() { + let input_complex = r#"{ + widget_name: { + value: "test" + children: [ + { child: {} } + { child: {} } + ] + } + }"#; + let expected = WidgetUse { + name: "widget_name".to_string(), + children: vec![ + ElementUse::Widget(WidgetUse::new("child".to_string(), vec![])), + ElementUse::Widget(WidgetUse::new("child".to_string(), vec![])), + ], + attrs: hashmap! { "value".to_string() => AttrValue::Concrete(PrimitiveValue::String("test".to_string()))}, + }; + assert_eq!( + WidgetUse::parse_hocon_hash( + parse_hocon(input_complex) + .unwrap() + .as_hash() + .unwrap() + .clone() + ) + .unwrap(), + expected + ); + } + + #[test] + fn test_parse_widget_definition() { + let input_complex = r#"{ + structure: { foo: {} } + }"#; + let expected = WidgetDefinition { + name: "widget_name".to_string(), + structure: ElementUse::Widget(WidgetUse::new("foo".to_string(), vec![])), + size: None, + }; + assert_eq!( + WidgetDefinition::parse_hocon( + "widget_name".to_string(), + &parse_hocon(input_complex).unwrap() + ) + .unwrap(), + expected + ); + } +} diff --git a/src/config/hocon_ext.rs b/src/config/hocon_ext.rs index ef90d3b..2593b53 100644 --- a/src/config/hocon_ext.rs +++ b/src/config/hocon_ext.rs @@ -1,24 +1,23 @@ +use anyhow::*; use hocon::Hocon; use std::collections::HashMap; pub trait HoconExt: Sized { - fn as_hash(&self) -> Option<&HashMap>; - fn as_array(&self) -> Option<&Vec>; + fn as_hash(&self) -> Result<&HashMap>; + fn as_array(&self) -> Result<&Vec>; } impl HoconExt for Hocon { - // TODO take owned self here? - - fn as_hash(&self) -> Option<&HashMap> { + fn as_hash(&self) -> Result<&HashMap> { match self { - Hocon::Hash(x) => Some(x), - _ => None, + Hocon::Hash(x) => Ok(x), + _ => Err(anyhow!("as_hash called with {:?}", self)), } } - fn as_array(&self) -> Option<&Vec> { + fn as_array(&self) -> Result<&Vec> { match self { - Hocon::Array(x) => Some(x), - _ => None, + Hocon::Array(x) => Ok(x), + _ => Err(anyhow!("as_array called with {:?}", self)), } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index abf8108..8b63569 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,50 +1,66 @@ +use crate::value::PrimitiveValue; use anyhow::*; +use element::*; use hocon::*; use hocon_ext::HoconExt; use std::collections::HashMap; use std::convert::TryFrom; use try_match::try_match; +pub mod element; pub mod hocon_ext; #[derive(Debug, Clone)] pub struct EwwConfig { widgets: HashMap, windows: HashMap, - default_vars: HashMap, + default_vars: HashMap, +} + +#[allow(unused)] +macro_rules! try_type { + ($typ:ty; $code:expr) => {{ + let x: $typ = try { $code }; + x + }}; + ($typ:ty; $code:block) => {{ + let x: $typ = try { $code }; + x + }}; } impl EwwConfig { pub fn from_hocon(hocon: &Hocon) -> Result { - let data = hocon - .as_hash() - .context("eww config has to be a map structure")?; + let data = hocon.as_hash()?; + + let widgets = data + .get("widgets") + .context("widgets field missing")? + .as_hash()? + .iter() + .map(|(n, def)| Ok((n.clone(), WidgetDefinition::parse_hocon(n.clone(), def)?))) + .collect::>()?; + + let windows = data + .get("windows") + .context("windows field missing")? + .as_hash()? + .iter() + .map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?))) + .collect::>()?; + + let default_vars = data + .get("default_vars") + .unwrap_or(&Hocon::Hash(HashMap::new())) + .as_hash()? + .iter() + .map(|(name, def)| Ok((name.clone(), PrimitiveValue::try_from(def)?))) + .collect::>()?; Ok(EwwConfig { - widgets: data - .get("widgets") - .context("widgets need to be provided")? - .as_hash() - .context("widgets need to be a map")? - .iter() - .map(|(name, def)| Ok((name.clone(), parse_widget_definition(name.clone(), def)?))) - .collect::>>()?, - windows: data - .get("windows") - .context("windows need to be provided")? - .as_hash() - .context("windows need to be a map")? - .iter() - .map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?))) - .collect::>>()?, - default_vars: data - .get("default_vars") - .unwrap_or(&Hocon::Hash(HashMap::new())) - .as_hash() - .context("default_vars needs to be a map")? - .iter() - .map(|(name, def)| Ok((name.clone(), AttrValue::try_from(def)?))) - .collect::>>()?, + widgets, + windows, + default_vars, }) } @@ -54,7 +70,7 @@ impl EwwConfig { pub fn get_windows(&self) -> &HashMap { &self.windows } - pub fn get_default_vars(&self) -> &HashMap { + pub fn get_default_vars(&self) -> &HashMap { &self.default_vars } } @@ -73,19 +89,19 @@ impl EwwWindowDefinition { .context("window config has to be a map structure")?; let position: Option<_> = try { ( - data.get("pos")?.as_hash()?.get("x")?.as_i64()? as i32, - data.get("pos")?.as_hash()?.get("y")?.as_i64()? as i32, + data.get("pos")?.as_hash().ok()?.get("x")?.as_i64()? as i32, + data.get("pos")?.as_hash().ok()?.get("y")?.as_i64()? as i32, ) }; let size: Option<_> = try { ( - data.get("size")?.as_hash()?.get("x")?.as_i64()? as i32, - data.get("size")?.as_hash()?.get("y")?.as_i64()? as i32, + data.get("size")?.as_hash().ok()?.get("x")?.as_i64()? as i32, + data.get("size")?.as_hash().ok()?.get("y")?.as_i64()? as i32, ) }; let element = - parse_element_use(data.get("widget").context("no widget use given")?.clone())?; + ElementUse::parse_hocon(data.get("widget").context("no widget use given")?.clone())?; Ok(EwwWindowDefinition { position: position.context("pos.x and pos.y need to be set")?, @@ -97,24 +113,34 @@ impl EwwWindowDefinition { #[derive(Clone, Debug, PartialEq)] pub enum AttrValue { - String(String), - Number(f64), - Boolean(bool), + Concrete(PrimitiveValue), VarRef(String), } impl AttrValue { - pub fn as_string(&self) -> Option<&String> { - try_match!(AttrValue::String(x) = self).ok() + pub fn as_string(&self) -> Result<&String> { + try_match!(AttrValue::Concrete(x) = self) + .map_err(|e| anyhow!("{:?} is not a string", e))? + .as_string() } - pub fn as_f64(&self) -> Option { - try_match!(AttrValue::Number(x) = self => *x).ok() + pub fn as_f64(&self) -> Result { + try_match!(AttrValue::Concrete(x) = self) + .map_err(|e| anyhow!("{:?} is not an f64", e))? + .as_f64() } - pub fn as_bool(&self) -> Option { - try_match!(AttrValue::Boolean(x) = self => *x).ok() + pub fn as_bool(&self) -> Result { + try_match!(AttrValue::Concrete(x) = self) + .map_err(|e| anyhow!("{:?} is not a bool", e))? + .as_bool() } - pub fn as_var_ref(&self) -> Option<&String> { - try_match!(AttrValue::VarRef(x) = self).ok() + pub fn as_var_ref(&self) -> Result<&String> { + try_match!(AttrValue::VarRef(x) = self).map_err(|e| anyhow!("{:?} is not a VarRef", e)) + } +} + +impl From for AttrValue { + fn from(value: PrimitiveValue) -> Self { + AttrValue::Concrete(value) } } @@ -125,165 +151,15 @@ impl std::convert::TryFrom<&Hocon> for AttrValue { Hocon::String(s) if s.starts_with("$$") => { AttrValue::VarRef(s.trim_start_matches("$$").to_string()) } - Hocon::String(s) => AttrValue::String(s.to_string()), - Hocon::Integer(n) => AttrValue::Number(*n as f64), - Hocon::Real(n) => AttrValue::Number(*n as f64), - Hocon::Boolean(b) => AttrValue::Boolean(*b), - _ => return Err(anyhow!("cannot convert {} to config::AttrValue")), + Hocon::String(s) => AttrValue::Concrete(PrimitiveValue::String(s.clone())), + Hocon::Integer(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)), + Hocon::Real(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)), + Hocon::Boolean(b) => AttrValue::Concrete(PrimitiveValue::Boolean(*b)), + _ => return Err(anyhow!("cannot convert {:?} to config::AttrValue", &value)), }) } } -#[derive(Debug, Clone, PartialEq)] -pub struct WidgetDefinition { - pub name: String, - pub structure: ElementUse, - pub size: Option<(i32, i32)>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ElementUse { - Widget(WidgetUse), - Text(String), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct WidgetUse { - pub name: String, - pub children: Vec, - pub attrs: HashMap, -} - -impl WidgetUse { - pub fn new(name: String, children: Vec) -> Self { - WidgetUse { - name, - children, - attrs: HashMap::new(), - } - } -} - -impl From for ElementUse { - fn from(other: WidgetUse) -> ElementUse { - ElementUse::Widget(other) - } -} - -pub fn parse_widget_definition(name: String, hocon: &Hocon) -> Result { - let definition = hocon - .as_hash() - .context("widget definition was not a hash")?; - let structure = definition - .get("structure") - .cloned() - .context("structure needs to be set") - .and_then(parse_element_use)?; - - Ok(WidgetDefinition { - name, - structure, - size: try { - ( - definition.get("size_x")?.as_i64()? as i32, - definition.get("size_y")?.as_i64()? as i32, - ) - }, - }) -} - -pub fn parse_element_use(hocon: Hocon) -> Result { - match hocon { - Hocon::String(s) => Ok(ElementUse::Text(s)), - Hocon::Hash(hash) => parse_widget_use(hash).map(ElementUse::Widget), - _ => Err(anyhow!("{:?} is not a valid element", hocon)), - } -} - -pub fn parse_widget_use(data: HashMap) -> Result { - let (widget_name, widget_config) = data.into_iter().next().unwrap(); - let widget_config = widget_config.as_hash().unwrap(); - - // TODO allow for `layout_horizontal: [ elements ]` shorthand - - let children = match &widget_config.get("children") { - Some(Hocon::String(text)) => Ok(vec![ElementUse::Text(text.to_string())]), - Some(Hocon::Array(children)) => children - .clone() - .into_iter() - .map(parse_element_use) - .collect::>>(), - None => Ok(Vec::new()), - _ => Err(anyhow!( - "children must be either a list of elements or a string, but was {:?}" - )), - }?; - - let attrs: HashMap = widget_config - .into_iter() - .filter_map(|(key, value)| Some((key.to_lowercase(), AttrValue::try_from(value).ok()?))) - .collect(); - - Ok(WidgetUse { - name: widget_name.to_string(), - children, - attrs, - }) -} - pub fn parse_hocon(s: &str) -> Result { Ok(HoconLoader::new().load_str(s)?.hocon()?) } - -#[cfg(test)] -mod test { - use super::*; - - const EXAMPLE_CONFIG: &'static str = r#"{ - name: "example_widget" - structure { - layout_horizontal { - children: [ - { text { children: "hi", color: "red" } } - { text: {} } - ] - } - } - }"#; - - #[test] - fn test_parse() { - assert_eq!( - parse_element_use(Hocon::String("hi".to_string())).unwrap(), - ElementUse::Text("hi".to_string()) - ); - } - - // #[test] - // fn test_parse_widget_definition() { - // let expected = WidgetDefinition { - // name: "example_widget".to_string(), - // structure: ElementUse::Widget(WidgetUse { - // name: "layout_horizontal".to_string(), - // attrs: HashMap::new(), - // children: vec![ - // ElementUse::Widget(WidgetUse::new( - // "text".to_string(), - // vec![ElementUse::Text("hi".to_string())], - // )), - // ElementUse::Widget(WidgetUse::new("text".to_string(), vec![])), - // ], - // }), - // }; - - // let parsed_hocon = parse_hocon("{ text: { children: \"hi\" } }").unwrap(); - // assert_eq!( - // parse_element_use(parsed_hocon).unwrap(), - // ElementUse::Widget(WidgetUse::new( - // "text".to_string(), - // vec![ElementUse::Text("hi".to_string())] - // )) - // ); - // assert_eq!(parse_widget_definition(EXAMPLE_CONFIG).unwrap(), expected); - // } -} diff --git a/src/main.rs b/src/main.rs index c253858..8985232 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,13 @@ use gtk::{Application, ApplicationWindow}; use std::{collections::HashMap, process::Command}; pub mod config; +pub mod value; pub mod widgets; +use config::element; +use config::AttrValue; +use value::PrimitiveValue; + const CMD_STRING_PLACEHODLER: &str = "{}"; const EXAMPLE_CONFIG: &str = r#"{ @@ -29,6 +34,13 @@ const EXAMPLE_CONFIG: &str = r#"{ ] } } + }, + test: { + structure: { + some_widget: { + some_value: "$$ooph" + } + } } }, default_vars: { @@ -41,8 +53,8 @@ const EXAMPLE_CONFIG: &str = r#"{ size.x: 500 size.y: 50 widget: { - some_widget: { - some_value: "$$ree" + test: { + ooph: "$$ree" } } } @@ -59,10 +71,16 @@ macro_rules! build { #[derive(Debug)] enum MuhhMsg { - UpdateValue(String, config::AttrValue), + UpdateValue(String, PrimitiveValue), } -fn main() -> Result<()> { +fn main() { + if let Err(e) = try_main() { + eprintln!("{:?}", e); + } +} + +fn try_main() -> Result<()> { let eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(EXAMPLE_CONFIG)?)?; dbg!(&eww_config); @@ -137,35 +155,35 @@ fn event_loop(sender: glib::Sender) { let mut x = 0; loop { x += 1; - std::thread::sleep_ms(1000); + std::thread::sleep(std::time::Duration::from_millis(1000)); sender .send(MuhhMsg::UpdateValue( "ree".to_string(), - config::AttrValue::Number(x as f64 * 10.0), + PrimitiveValue::Number(x as f64 * 10.0), )) .unwrap(); } } fn element_to_gtk_thing( - widget_definitions: &HashMap, + widget_definitions: &HashMap, eww_state: &mut EwwState, - local_environment: &HashMap, - element: &config::ElementUse, + local_environment: &HashMap, + element: &element::ElementUse, ) -> Option { match element { - config::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()), - config::ElementUse::Widget(widget) => { + element::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()), + element::ElementUse::Widget(widget) => { widget_use_to_gtk_thing(widget_definitions, eww_state, local_environment, widget) } } } fn widget_use_to_gtk_thing( - widget_definitions: &HashMap, + widget_definitions: &HashMap, eww_state: &mut EwwState, - local_environment: &HashMap, - widget: &config::WidgetUse, + local_environment: &HashMap, + widget: &element::WidgetUse, ) -> Option { let gtk_widget = widget_use_to_gtk_container(widget_definitions, eww_state, &local_environment, &widget) @@ -178,7 +196,7 @@ fn widget_use_to_gtk_thing( if let Some(css_class) = widget .attrs .get("class") - .and_then(config::AttrValue::as_string) + .and_then(|x| AttrValue::as_string(x).ok()) { gtk_widget.get_style_context().add_class(css_class); } @@ -187,10 +205,10 @@ fn widget_use_to_gtk_thing( } fn widget_use_to_gtk_container( - widget_definitions: &HashMap, + widget_definitions: &HashMap, eww_state: &mut EwwState, - local_environment: &HashMap, - widget: &config::WidgetUse, + local_environment: &HashMap, + widget: &element::WidgetUse, ) -> Option { let container_widget: gtk::Container = match widget.name.as_str() { "layout_horizontal" => gtk::Box::new(gtk::Orientation::Horizontal, 0).upcast(), @@ -210,10 +228,10 @@ fn widget_use_to_gtk_container( } fn widget_use_to_gtk_widget( - widget_definitions: &HashMap, + widget_definitions: &HashMap, eww_state: &mut EwwState, - local_env: &HashMap, - widget: &config::WidgetUse, + local_env: &HashMap, + widget: &element::WidgetUse, ) -> Option { let new_widget: gtk::Widget = match widget.name.as_str() { "slider" => { @@ -266,18 +284,18 @@ fn widget_use_to_gtk_widget( #[derive(Default)] struct EwwState { - on_change_handlers: HashMap>>, - state: HashMap, + on_change_handlers: HashMap>>, + state: HashMap, } impl EwwState { - pub fn from_default_vars(defaults: HashMap) -> Self { + pub fn from_default_vars(defaults: HashMap) -> Self { EwwState { state: defaults, ..EwwState::default() } } - pub fn update_value(&mut self, key: String, value: config::AttrValue) { + pub fn update_value(&mut self, key: String, value: PrimitiveValue) { if let Some(handlers) = self.on_change_handlers.get(&key) { for on_change in handlers { on_change(value.clone()); @@ -286,59 +304,70 @@ impl EwwState { self.state.insert(key, value); } - pub fn resolve( + pub fn resolve( &mut self, - local_env: &HashMap, - value: &config::AttrValue, + local_env: &HashMap, + value: &AttrValue, set_value: F, ) -> bool { dbg!("resolve: ", value); - if let config::AttrValue::VarRef(name) = value { - if let Some(value) = self.state.get(name).cloned() { - self.on_change_handlers - .entry(name.to_string()) - .or_insert_with(Vec::new) - .push(Box::new(set_value.clone())); - self.resolve(local_env, &value, set_value) - } else if let Some(value) = local_env.get(name).cloned() { - self.resolve(local_env, &value, set_value) - } else { - false + match value { + AttrValue::VarRef(name) => { + if let Some(value) = self.state.get(name).cloned() { + self.on_change_handlers + .entry(name.to_string()) + .or_insert_with(Vec::new) + .push(Box::new(set_value.clone())); + self.resolve(local_env, &value.into(), set_value) + } else if let Some(value) = local_env.get(name).cloned() { + self.resolve(local_env, &value, set_value) + } else { + false + } + } + AttrValue::Concrete(value) => { + set_value(value.clone()); + true } - } else { - set_value(value.clone()); - true } } pub fn resolve_f64( &mut self, - local_env: &HashMap, - value: &config::AttrValue, + local_env: &HashMap, + value: &AttrValue, set_value: F, ) -> bool { self.resolve(local_env, value, move |x| { - x.as_f64().map(|v| set_value(v)); + if let Err(e) = x.as_f64().map(|v| set_value(v)) { + eprintln!("error while resolving value: {}", e); + }; }) } + + #[allow(dead_code)] pub fn resolve_bool( &mut self, - local_env: &HashMap, - value: &config::AttrValue, + local_env: &HashMap, + value: &AttrValue, set_value: F, ) -> bool { self.resolve(local_env, value, move |x| { - x.as_bool().map(|v| set_value(v)); + if let Err(e) = x.as_bool().map(|v| set_value(v)) { + eprintln!("error while resolving value: {}", e); + }; }) } pub fn resolve_string( &mut self, - local_env: &HashMap, - value: &config::AttrValue, + local_env: &HashMap, + value: &AttrValue, set_value: F, ) -> bool { self.resolve(local_env, value, move |x| { - x.as_string().map(|s| set_value(s.clone())); + if let Err(e) = x.as_string().map(|v| set_value(v.clone())) { + eprintln!("error while resolving value: {}", e); + }; }) } } diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..ed22fb0 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,46 @@ +use anyhow::*; +use derive_more::From; +use hocon::Hocon; +use try_match::try_match; + +#[derive(Clone, Debug, PartialEq, From)] +pub enum PrimitiveValue { + String(String), + Number(f64), + Boolean(bool), +} + +impl PrimitiveValue { + pub fn as_string(&self) -> Result<&String> { + try_match!(PrimitiveValue::String(x) = self).map_err(|x| anyhow!("{:?} is not a string", x)) + } + pub fn as_f64(&self) -> Result { + try_match!(PrimitiveValue::Number(x) = self) + .map_err(|x| anyhow!("{:?} is not an f64", x)) + .map(|&x| x) + } + pub fn as_bool(&self) -> Result { + try_match!(PrimitiveValue::Boolean(x) = self) + .map_err(|x| anyhow!("{:?} is not a bool", x)) + .map(|&x| x) + } +} + +impl std::convert::TryFrom<&Hocon> for PrimitiveValue { + type Error = anyhow::Error; + fn try_from(value: &Hocon) -> Result { + Ok(match value { + Hocon::String(s) if s.starts_with("$$") => { + return Err(anyhow!( + "Tried to use variable reference {} as primitive value", + s + )) + } + Hocon::String(s) => PrimitiveValue::String(s.to_string()), + Hocon::Integer(n) => PrimitiveValue::Number(*n as f64), + Hocon::Real(n) => PrimitiveValue::Number(*n as f64), + Hocon::Boolean(b) => PrimitiveValue::Boolean(*b), + _ => return Err(anyhow!("cannot convert {} to config::PrimitiveValue")), + }) + } +}