generally finished XML implementation. TODO: error handling, remove old code

This commit is contained in:
elkowar 2020-10-03 13:27:16 +02:00
parent d1c991ba92
commit 50b8840c5b
8 changed files with 297 additions and 146 deletions

1
Cargo.lock generated
View file

@ -334,6 +334,7 @@ dependencies = [
"hocon", "hocon",
"hotwatch", "hotwatch",
"ipc-channel", "ipc-channel",
"itertools",
"maplit", "maplit",
"num", "num",
"pretty_assertions", "pretty_assertions",

View file

@ -29,6 +29,7 @@ crossbeam-channel = "0.4"
num = "0.3" num = "0.3"
stoppable_thread = "0.2" stoppable_thread = "0.2"
roxmltree = "0.13" roxmltree = "0.13"
itertools = "0.9"
#thiserror = "1.0" #thiserror = "1.0"

View file

@ -14,32 +14,23 @@ pub struct WidgetDefinition {
} }
impl WidgetDefinition { impl WidgetDefinition {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> { pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
if !xml.is_element() { if xml.tag_name() != "def" {
bail!("Tried to parse element of type {:?} as Widget definition", xml.node_type());
} else if xml.tag_name().name().to_lowercase() != "def" {
bail!( bail!(
"Illegal element: only <def> may be used in definition block, but found '{}'", "Illegal element: only <def> may be used in definition block, but found '{}'",
xml.tag_name().name() xml.as_tag_string()
);
} else if xml.children().count() != 1 {
bail!(
"Widget definition '{}' needs to contain exactly one element",
xml.tag_name().name()
); );
} }
let size: Option<Result<_>> = xml
.child("size")
.ok()
.map(|node| Ok((node.attr("x")?.parse()?, node.attr("y")?.parse()?)));
Ok(WidgetDefinition { Ok(WidgetDefinition {
name: xml.try_attribute("name")?.to_owned(), name: xml.attr("name")?.to_owned(),
size: size.transpose()?,
size: if let Some(node) = xml.children().find(|child| child.tag_name().name() == "size") { structure: WidgetUse::from_xml_node(xml.only_child()?)?,
Some((node.try_attribute("x")?.parse()?, node.try_attribute("y")?.parse()?))
} else {
None
},
// we can unwrap here, because we previously verified that there is exactly one child
structure: WidgetUse::from_xml(xml.first_child().unwrap())?,
}) })
} }
@ -72,35 +63,19 @@ pub struct WidgetUse {
} }
impl WidgetUse { impl WidgetUse {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> { pub fn from_xml_node(xml: XmlNode) -> Result<Self> {
match xml.node_type() { match xml {
roxmltree::NodeType::Text => Ok(WidgetUse::simple_text(AttrValue::parse_string( XmlNode::Text(text) => Ok(WidgetUse::simple_text(AttrValue::parse_string(text.text()))),
xml.text() XmlNode::Element(elem) => Ok(WidgetUse {
.context("couldn't get text from node")? name: elem.tag_name().to_string(),
.trim_matches('\n') children: elem.children().map(WidgetUse::from_xml_node).collect::<Result<_>>()?,
.trim() attrs: elem
.to_owned(),
))),
roxmltree::NodeType::Element => {
let widget_name = xml.tag_name();
let attrs = xml
.attributes() .attributes()
.iter() .iter()
.map(|attr| (attr.name().to_owned(), AttrValue::parse_string(attr.value().to_owned()))) .map(|attr| (attr.name().to_owned(), AttrValue::parse_string(attr.value().to_owned())))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>(),
let children = xml }),
.children() XmlNode::Ignored(_) => Err(anyhow!("Failed to parse node {:?} as widget use", xml)),
.filter(|child| !child.is_comment())
.filter(|child| !(child.is_text() && child.text().unwrap().trim().trim_matches('\n').is_empty()))
.map(|child| WidgetUse::from_xml(child))
.collect::<Result<_>>()?;
Ok(WidgetUse {
name: widget_name.name().to_string(),
attrs,
children,
})
}
_ => Err(anyhow!("Tried to parse node of type {:?} as widget use", xml.node_type())),
} }
} }
} }
@ -186,80 +161,80 @@ pub fn parse_widget_use_children(children: Hocon) -> Result<Vec<WidgetUse>> {
} }
} }
#[cfg(test)] //#[cfg(test)]
mod test { //mod test {
use super::*; //use super::*;
use maplit::hashmap; //use maplit::hashmap;
use pretty_assertions::assert_eq; //use pretty_assertions::assert_eq;
#[test] //#[test]
fn test_parse_widget_use() { //fn test_parse_widget_use() {
let input_complex = r#"{ //let input_complex = r#"{
widget_name: { //widget_name: {
value: "test" //value: "test"
children: [ //children: [
{ child: {} } //{ child: {} }
{ child: { children: ["hi"] } } //{ child: { children: ["hi"] } }
] //]
} //}
}"#; //}"#;
let expected = WidgetUse { //let expected = WidgetUse {
name: "widget_name".to_string(), //name: "widget_name".to_string(),
children: vec![ //children: vec![
WidgetUse::new("child".to_string(), vec![]), //WidgetUse::new("child".to_string(), vec![]),
WidgetUse::new( //WidgetUse::new(
"child".to_string(), //"child".to_string(),
vec![WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::String( //vec![WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::String(
"hi".to_string(), //"hi".to_string(),
)))], //)))],
), //),
], //],
attrs: hashmap! { "value".to_string() => AttrValue::Concrete(PrimitiveValue::String("test".to_string()))}, //attrs: hashmap! { "value".to_string() => AttrValue::Concrete(PrimitiveValue::String("test".to_string()))},
}; //};
assert_eq!( //assert_eq!(
WidgetUse::parse_hocon(parse_hocon(input_complex).unwrap().clone()).unwrap(), //WidgetUse::parse_hocon(parse_hocon(input_complex).unwrap().clone()).unwrap(),
expected //expected
); //);
} //}
#[test] //#[test]
fn test_parse_widget_definition() { //fn test_parse_widget_definition() {
let input_complex = r#"{ //let input_complex = r#"{
structure: { foo: {} } //structure: { foo: {} }
}"#; //}"#;
let expected = WidgetDefinition { //let expected = WidgetDefinition {
name: "widget_name".to_string(), //name: "widget_name".to_string(),
structure: WidgetUse::new("foo".to_string(), vec![]), //structure: WidgetUse::new("foo".to_string(), vec![]),
size: None, //size: None,
}; //};
assert_eq!( //assert_eq!(
WidgetDefinition::parse_hocon("widget_name".to_string(), &parse_hocon(input_complex).unwrap()).unwrap(), //WidgetDefinition::parse_hocon("widget_name".to_string(), &parse_hocon(input_complex).unwrap()).unwrap(),
expected //expected
); //);
} //}
#[test] //#[test]
fn test_parse_widget_use_xml() { //fn test_parse_widget_use_xml() {
let input = r#" //let input = r#"
<widget_name attr1="hi" attr2="12"> //<widget_name attr1="hi" attr2="12">
<child_widget/> //<child_widget/>
foo //foo
</widget_name> //</widget_name>
"#; //"#;
let document = roxmltree::Document::parse(input).unwrap(); //let document = roxmltree::Document::parse(input).unwrap();
let xml = document.root_element().clone(); //let xml = document.root_element().clone();
let expected = WidgetUse { //let expected = WidgetUse {
name: "widget_name".to_owned(), //name: "widget_name".to_owned(),
attrs: hashmap! { //attrs: hashmap! {
"attr1".to_owned() => AttrValue::Concrete(PrimitiveValue::String("hi".to_owned())), //"attr1".to_owned() => AttrValue::Concrete(PrimitiveValue::String("hi".to_owned())),
"attr2".to_owned() => AttrValue::Concrete(PrimitiveValue::Number(12f64)), //"attr2".to_owned() => AttrValue::Concrete(PrimitiveValue::Number(12f64)),
}, //},
children: vec![ //children: vec![
WidgetUse::new("child_widget".to_owned(), Vec::new()), //WidgetUse::new("child_widget".to_owned(), Vec::new()),
WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::String("foo".to_owned()))), //WidgetUse::simple_text(AttrValue::Concrete(PrimitiveValue::String("foo".to_owned()))),
], //],
}; //};
assert_eq!(expected, WidgetUse::from_xml(xml).unwrap()); //assert_eq!(expected, WidgetUse::from_xml(xml).unwrap());
} //}
} //}

View file

@ -34,48 +34,50 @@ impl EwwConfig {
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> { pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
let content = std::fs::read_to_string(path)?; let content = std::fs::read_to_string(path)?;
let document = roxmltree::Document::parse(&content)?; let document = roxmltree::Document::parse(&content)?;
EwwConfig::from_xml(document.root_element()) EwwConfig::from_xml_element(XmlNode::from(document.root_element()).as_element()?)
} }
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> { pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
let definitions = xml let definitions = xml
.find_child_with_tag("definitions")? .child("definitions")?
.children() .child_elements()
.map(|child| { .map(|child| {
let def = WidgetDefinition::from_xml(child)?; let def = WidgetDefinition::from_xml_element(child)?;
Ok((def.name.clone(), def)) Ok((def.name.clone(), def))
}) })
.collect::<Result<HashMap<_, _>>>() .collect::<Result<HashMap<_, _>>>()
.context("error parsing widget definitions")?; .context("error parsing widget definitions")?;
let windows = xml let windows = xml
.find_child_with_tag("windows")? .child("windows")?
.children() .child_elements()
.map(|child| Ok((child.try_attribute("name")?.to_owned(), EwwWindowDefinition::from_xml(child)?))) .map(|child| Ok((child.attr("name")?.to_owned(), EwwWindowDefinition::from_xml_element(child)?)))
.collect::<Result<HashMap<_, _>>>() .collect::<Result<HashMap<_, _>>>()
.context("error parsing window definitions")?; .context("error parsing window definitions")?;
let default_vars = xml let default_vars = xml
.find_child_with_tag("variables") .child("variables")
.ok()
.map(|variables_node| { .map(|variables_node| {
variables_node variables_node
.children() .child_elements()
.map(|child| { .map(|child| {
Some(( Ok((
child.tag_name().name().to_owned(), child.tag_name().to_owned(),
PrimitiveValue::parse_string(child.text()?.trim_matches('\n').trim()), PrimitiveValue::parse_string(&child.only_child()?.as_text()?.text()),
)) ))
}) })
.collect::<Option<HashMap<_, _>>>() .collect::<Result<HashMap<_, _>>>()
}) })
.unwrap_or_default() .transpose()
.context("error parsing default variable value")?; .context("error parsing default variable value")?
.unwrap_or_default();
Ok(EwwConfig { Ok(dbg!(EwwConfig {
widgets: definitions, widgets: definitions,
windows, windows,
default_vars, default_vars,
}) }))
} }
pub fn from_hocon(hocon: &Hocon) -> Result<Self> { pub fn from_hocon(hocon: &Hocon) -> Result<Self> {
@ -131,20 +133,20 @@ pub struct EwwWindowDefinition {
} }
impl EwwWindowDefinition { impl EwwWindowDefinition {
pub fn from_xml(xml: roxmltree::Node) -> Result<Self> { pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
if xml.tag_name().name() != "window" { if xml.tag_name() != "window" {
bail!( bail!(
"Only <window> tags are valid window definitions, but found {}", "Only <window> tags are valid window definitions, but found {}",
xml.tag_name().name() xml.as_tag_string()
); );
} }
let size_node = xml.find_child_with_tag("size")?; let size_node = xml.child("size")?;
let size = (size_node.try_attribute("x")?.parse()?, size_node.try_attribute("y")?.parse()?); let size = (size_node.attr("x")?.parse()?, size_node.attr("y")?.parse()?);
let pos_node = xml.find_child_with_tag("pos")?; let pos_node = xml.child("pos")?;
let position = (pos_node.try_attribute("x")?.parse()?, pos_node.try_attribute("y")?.parse()?); let position = (pos_node.attr("x")?.parse()?, pos_node.attr("y")?.parse()?);
let widget = WidgetUse::from_xml(xml.find_child_with_tag("widget")?)?; let widget = WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?;
Ok(EwwWindowDefinition { position, size, widget }) Ok(EwwWindowDefinition { position, size, widget })
} }

View file

@ -1,5 +1,8 @@
use crate::util::StringExt;
use anyhow::*; use anyhow::*;
use extend::ext; use extend::ext;
use itertools::Itertools;
use std::fmt;
#[ext(pub)] #[ext(pub)]
impl<'a, 'b> roxmltree::Node<'a, 'b> { impl<'a, 'b> roxmltree::Node<'a, 'b> {
@ -17,3 +20,145 @@ impl<'a, 'b> roxmltree::Node<'a, 'b> {
.with_context(|| anyhow!("attribute '{}' missing from '{}'", key, self.tag_name().name())) .with_context(|| anyhow!("attribute '{}' missing from '{}'", key, self.tag_name().name()))
} }
} }
#[derive(Debug)]
pub enum XmlNode<'a, 'b> {
Element(XmlElement<'a, 'b>),
Text(XmlText<'a, 'b>),
Ignored(roxmltree::Node<'a, 'b>),
}
impl<'a, 'b> fmt::Display for XmlNode<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
XmlNode::Text(text) => write!(f, "{}", text),
XmlNode::Element(elem) => write!(f, "{}", elem),
XmlNode::Ignored(node) => write!(f, "{:?}", node),
}
}
}
#[derive(Debug)]
pub struct XmlElement<'a, 'b>(roxmltree::Node<'a, 'b>);
impl<'a, 'b> fmt::Display for XmlElement<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let children = self
.children()
.map(|child| format!("{}", child))
.map(|x| x.lines().map(|line| format!(" {}", line)).join("\n"))
.join("\n");
write!(f, "{}{}</{}>", self.as_tag_string(), children, self.tag_name())
}
}
#[derive(Debug)]
pub struct XmlText<'a, 'b>(roxmltree::Node<'a, 'b>);
impl<'a, 'b> fmt::Display for XmlText<'a, 'b> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Text({})", self.0.text().unwrap_or_default())
}
}
impl<'a, 'b> XmlNode<'a, 'b> {
pub fn as_text(self) -> Result<XmlText<'a, 'b>> {
match self {
XmlNode::Text(text) => Ok(text),
_ => Err(anyhow!("'{}' is not a text node", self)),
}
}
pub fn as_element(self) -> Result<XmlElement<'a, 'b>> {
match self {
XmlNode::Element(element) => Ok(element),
_ => Err(anyhow!("'{}' is not an element node", self)),
}
}
}
impl<'a, 'b> XmlText<'a, 'b> {
pub fn text(&self) -> String {
self.0.text().unwrap_or_default().trim_lines().trim_matches('\n').to_owned()
}
}
impl<'a, 'b> XmlElement<'a, 'b> {
pub fn as_tag_string(&self) -> String {
let attrs = self
.attributes()
.iter()
.map(|attr| format!("{}=\"{}\"", attr.name(), attr.value()))
.join(" ");
format!("<{} {}>", self.tag_name(), attrs)
}
pub fn tag_name(&self) -> &str {
self.0.tag_name().name()
}
pub fn child(&self, tagname: &str) -> Result<XmlElement> {
self.child_elements()
.find(|child| child.tag_name() == tagname)
.with_context(|| anyhow!("child element '{}' missing from {}", tagname, self.as_tag_string()))
}
pub fn children(&self) -> impl Iterator<Item = XmlNode> {
self.0
.children()
.filter(|child| child.is_element() || (child.is_text() && !child.text().unwrap_or_default().is_blank()))
.map(XmlNode::from)
}
pub fn child_elements(&self) -> impl Iterator<Item = XmlElement> {
self.0.children().filter(|child| child.is_element()).map(XmlElement)
}
pub fn attributes(&self) -> &[roxmltree::Attribute] {
self.0.attributes()
}
pub fn attr(&self, key: &str) -> Result<&str> {
self.0
.attribute(key)
.with_context(|| anyhow!("'{}' missing attribute '{}'", self.as_tag_string(), key))
}
pub fn only_child(&self) -> Result<XmlNode> {
let mut children_iter = self.children();
let only_child = children_iter
.next()
.context(anyhow!("'{}' had no children", self.as_tag_string()))?;
if children_iter.next().is_some() {
bail!("'{}' had more than one child", &self);
}
Ok(only_child)
}
pub fn only_child_element(&self) -> Result<XmlElement> {
Ok(self.only_child()?.as_element()?)
}
}
impl<'a, 'b> From<XmlElement<'a, 'b>> for XmlNode<'a, 'b> {
fn from(elem: XmlElement<'a, 'b>) -> Self {
XmlNode::Element(elem)
}
}
impl<'a, 'b> From<XmlText<'a, 'b>> for XmlNode<'a, 'b> {
fn from(elem: XmlText<'a, 'b>) -> Self {
XmlNode::Text(elem)
}
}
impl<'a, 'b> From<roxmltree::Node<'a, 'b>> for XmlNode<'a, 'b> {
fn from(node: roxmltree::Node<'a, 'b>) -> Self {
if node.is_text() {
XmlNode::Text(XmlText(node))
} else if node.is_element() | node.is_root() {
XmlNode::Element(XmlElement(node))
} else {
XmlNode::Ignored(node)
}
}
}

View file

@ -63,6 +63,7 @@ impl EwwState {
// get value from local // get value from local
self.resolve(local_env, &value, set_value) self.resolve(local_env, &value, set_value)
} else { } else {
eprintln!("WARN: unknown variable '{}' was referenced", name);
false false
} }
} }

View file

@ -1,5 +1,7 @@
use anyhow::*; use anyhow::*;
use extend::ext;
use grass; use grass;
use itertools::Itertools;
use std::path::Path; use std::path::Path;
pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> { pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
@ -7,3 +9,16 @@ pub fn parse_scss_from_file<P: AsRef<Path>>(path: P) -> Result<String> {
grass::from_string(scss_content, &grass::Options::default()) grass::from_string(scss_content, &grass::Options::default())
.map_err(|err| anyhow!("encountered SCSS parsing error: {:?}", err)) .map_err(|err| anyhow!("encountered SCSS parsing error: {:?}", err))
} }
#[ext(pub, name = StringExt)]
impl<T: AsRef<str>> T {
/// check if the string is empty after removing all linebreaks and trimming whitespace
fn is_blank(self) -> bool {
self.as_ref().replace('\n', "").trim().is_empty()
}
/// trim all lines in a string
fn trim_lines(self) -> String {
self.as_ref().lines().map(|line| line.trim()).join("\n")
}
}

View file

@ -58,6 +58,7 @@ pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<Option<gtk
"image" => build_gtk_image(bargs)?.upcast(), "image" => build_gtk_image(bargs)?.upcast(),
"button" => build_gtk_button(bargs)?.upcast(), "button" => build_gtk_button(bargs)?.upcast(),
"label" => build_gtk_label(bargs)?.upcast(), "label" => build_gtk_label(bargs)?.upcast(),
"text" => build_gtk_text(bargs)?.upcast(),
_ => return Ok(None), _ => return Ok(None),
}; };
Ok(Some(gtk_widget)) Ok(Some(gtk_widget))
@ -107,11 +108,21 @@ fn build_gtk_layout(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> { fn build_gtk_label(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
let gtk_widget = gtk::Label::new(None); let gtk_widget = gtk::Label::new(None);
resolve!(bargs, gtk_widget, { resolve!(bargs, gtk_widget, {
resolve_str => "text" = 10.0 => |v| gtk_widget.set_text(&v), resolve_str => "text" => |v| gtk_widget.set_text(&v),
}); });
Ok(gtk_widget) Ok(gtk_widget)
} }
fn build_gtk_text(bargs: &mut BuilderArgs) -> Result<gtk::Label> {
let text = bargs.widget.children.first().unwrap().get_attr("text")?;
let gtk_widget = gtk::Label::new(None);
bargs.eww_state.resolve_str(
bargs.local_env,
text,
glib::clone!(@strong gtk_widget => move |v| gtk_widget.set_text(&v)),
);
Ok(gtk_widget)
}
fn parse_orientation(o: &str) -> gtk::Orientation { fn parse_orientation(o: &str) -> gtk::Orientation {
match o { match o {
"vertical" | "v" => gtk::Orientation::Vertical, "vertical" | "v" => gtk::Orientation::Vertical,