cleanup
This commit is contained in:
parent
43b431c2dc
commit
1969b6eb56
7 changed files with 413 additions and 269 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
171
src/config/element.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
}
|
||||
|
|
109
src/main.rs
109
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<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
46
src/value.rs
Normal 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")),
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue