This commit is contained in:
elkowar 2020-09-20 21:02:55 +02:00
parent 43b431c2dc
commit 1969b6eb56
7 changed files with 413 additions and 269 deletions

36
Cargo.lock generated
View file

@ -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"

View file

@ -24,3 +24,6 @@ anyhow = "1.0"
pretty_assertions = "0.6.1"
derive_more = "0.99"
maplit = "1"

171
src/config/element.rs Normal file
View file

@ -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<Self> {
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<Self> {
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<ElementUse>,
pub attrs: HashMap<String, AttrValue>,
}
impl WidgetUse {
pub fn new(name: String, children: Vec<ElementUse>) -> Self {
WidgetUse {
name,
children,
attrs: HashMap::new(),
}
}
pub fn parse_hocon_hash(data: HashMap<String, Hocon>) -> Result<WidgetUse> {
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::<Result<Vec<_>>>(),
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<WidgetUse> 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
);
}
}

View file

@ -1,24 +1,23 @@
use anyhow::*;
use hocon::Hocon;
use std::collections::HashMap;
pub trait HoconExt: Sized {
fn as_hash(&self) -> Option<&HashMap<String, Self>>;
fn as_array(&self) -> Option<&Vec<Self>>;
fn as_hash(&self) -> Result<&HashMap<String, Self>>;
fn as_array(&self) -> Result<&Vec<Self>>;
}
impl HoconExt for Hocon {
// TODO take owned self here?
fn as_hash(&self) -> Option<&HashMap<String, Self>> {
fn as_hash(&self) -> Result<&HashMap<String, Self>> {
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<Self>> {
fn as_array(&self) -> Result<&Vec<Self>> {
match self {
Hocon::Array(x) => Some(x),
_ => None,
Hocon::Array(x) => Ok(x),
_ => Err(anyhow!("as_array called with {:?}", self)),
}
}
}

View file

@ -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<String, WidgetDefinition>,
windows: HashMap<String, EwwWindowDefinition>,
default_vars: HashMap<String, AttrValue>,
default_vars: HashMap<String, PrimitiveValue>,
}
#[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<EwwConfig> {
let data = hocon
.as_hash()
.context("eww config has to be a map structure")?;
let data = hocon.as_hash()?;
Ok(EwwConfig {
widgets: data
let widgets = data
.get("widgets")
.context("widgets need to be provided")?
.as_hash()
.context("widgets need to be a map")?
.context("widgets field missing")?
.as_hash()?
.iter()
.map(|(name, def)| Ok((name.clone(), parse_widget_definition(name.clone(), def)?)))
.collect::<Result<HashMap<String, WidgetDefinition>>>()?,
windows: data
.map(|(n, def)| Ok((n.clone(), WidgetDefinition::parse_hocon(n.clone(), def)?)))
.collect::<Result<_>>()?;
let windows = data
.get("windows")
.context("windows need to be provided")?
.as_hash()
.context("windows need to be a map")?
.context("windows field missing")?
.as_hash()?
.iter()
.map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?)))
.collect::<Result<HashMap<String, EwwWindowDefinition>>>()?,
default_vars: data
.collect::<Result<_>>()?;
let default_vars = data
.get("default_vars")
.unwrap_or(&Hocon::Hash(HashMap::new()))
.as_hash()
.context("default_vars needs to be a map")?
.as_hash()?
.iter()
.map(|(name, def)| Ok((name.clone(), AttrValue::try_from(def)?)))
.collect::<Result<HashMap<_, _>>>()?,
.map(|(name, def)| Ok((name.clone(), PrimitiveValue::try_from(def)?)))
.collect::<Result<_>>()?;
Ok(EwwConfig {
widgets,
windows,
default_vars,
})
}
@ -54,7 +70,7 @@ impl EwwConfig {
pub fn get_windows(&self) -> &HashMap<String, EwwWindowDefinition> {
&self.windows
}
pub fn get_default_vars(&self) -> &HashMap<String, AttrValue> {
pub fn get_default_vars(&self) -> &HashMap<String, PrimitiveValue> {
&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<f64> {
try_match!(AttrValue::Number(x) = self => *x).ok()
pub fn as_f64(&self) -> Result<f64> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not an f64", e))?
.as_f64()
}
pub fn as_bool(&self) -> Option<bool> {
try_match!(AttrValue::Boolean(x) = self => *x).ok()
pub fn as_bool(&self) -> Result<bool> {
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<PrimitiveValue> 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<ElementUse>,
pub attrs: HashMap<String, AttrValue>,
}
impl WidgetUse {
pub fn new(name: String, children: Vec<ElementUse>) -> Self {
WidgetUse {
name,
children,
attrs: HashMap::new(),
}
}
}
impl From<WidgetUse> for ElementUse {
fn from(other: WidgetUse) -> ElementUse {
ElementUse::Widget(other)
}
}
pub fn parse_widget_definition(name: String, hocon: &Hocon) -> Result<WidgetDefinition> {
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<ElementUse> {
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<String, Hocon>) -> Result<WidgetUse> {
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::<Result<Vec<_>>>(),
None => Ok(Vec::new()),
_ => Err(anyhow!(
"children must be either a list of elements or a string, but was {:?}"
)),
}?;
let attrs: HashMap<String, AttrValue> = 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<Hocon> {
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);
// }
}

View file

@ -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<MuhhMsg>) {
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<String, config::WidgetDefinition>,
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
element: &config::ElementUse,
local_environment: &HashMap<String, AttrValue>,
element: &element::ElementUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
local_environment: &HashMap<String, AttrValue>,
widget: &element::WidgetUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
local_environment: &HashMap<String, AttrValue>,
widget: &element::WidgetUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
local_env: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
local_env: &HashMap<String, AttrValue>,
widget: &element::WidgetUse,
) -> Option<gtk::Widget> {
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<String, Vec<Box<dyn Fn(config::AttrValue) + 'static>>>,
state: HashMap<String, config::AttrValue>,
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>,
state: HashMap<String, PrimitiveValue>,
}
impl EwwState {
pub fn from_default_vars(defaults: HashMap<String, config::AttrValue>) -> Self {
pub fn from_default_vars(defaults: HashMap<String, PrimitiveValue>) -> 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<F: Fn(config::AttrValue) + 'static + Clone>(
pub fn resolve<F: Fn(PrimitiveValue) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
value: &config::AttrValue,
local_env: &HashMap<String, AttrValue>,
value: &AttrValue,
set_value: F,
) -> bool {
dbg!("resolve: ", value);
if let config::AttrValue::VarRef(name) = value {
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, set_value)
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
}
} else {
}
AttrValue::Concrete(value) => {
set_value(value.clone());
true
}
}
}
pub fn resolve_f64<F: Fn(f64) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
value: &config::AttrValue,
local_env: &HashMap<String, AttrValue>,
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<F: Fn(bool) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
value: &config::AttrValue,
local_env: &HashMap<String, AttrValue>,
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<F: Fn(String) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
value: &config::AttrValue,
local_env: &HashMap<String, AttrValue>,
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);
};
})
}
}

46
src/value.rs Normal file
View file

@ -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<f64> {
try_match!(PrimitiveValue::Number(x) = self)
.map_err(|x| anyhow!("{:?} is not an f64", x))
.map(|&x| x)
}
pub fn as_bool(&self) -> Result<bool> {
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<Self> {
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")),
})
}
}