generally finished XML implementation. TODO: error handling, remove old code
This commit is contained in:
parent
d1c991ba92
commit
50b8840c5b
8 changed files with 297 additions and 146 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -334,6 +334,7 @@ dependencies = [
|
||||||
"hocon",
|
"hocon",
|
||||||
"hotwatch",
|
"hotwatch",
|
||||||
"ipc-channel",
|
"ipc-channel",
|
||||||
|
"itertools",
|
||||||
"maplit",
|
"maplit",
|
||||||
"num",
|
"num",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/util.rs
15
src/util.rs
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue