State refactor (#121)

* Add widget_node trait and generic impl

* Use WidgetNode stuff in some places

* Use WidgetNode stuff in rendering code

* Refactor
This commit is contained in:
ElKowar 2021-02-26 21:03:53 +01:00 committed by GitHub
parent 6251a2bc1d
commit a18901f187
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 403 additions and 287 deletions

7
Cargo.lock generated
View file

@ -230,6 +230,12 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "dyn-clone"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -258,6 +264,7 @@ dependencies = [
"bincode", "bincode",
"debug_stub_derive", "debug_stub_derive",
"derive_more", "derive_more",
"dyn-clone",
"extend", "extend",
"futures-core", "futures-core",
"futures-util", "futures-util",

View file

@ -55,6 +55,8 @@ futures-core = "0.3"
futures-util = "0.3" futures-util = "0.3"
tokio-util = "0.6" tokio-util = "0.6"
dyn-clone = "1.0"
[target.'cfg(target_os="linux")'.dependencies] [target.'cfg(target_os="linux")'.dependencies]
inotify = "0.9" inotify = "0.9"

View file

@ -3,8 +3,7 @@ use crate::{
config::{window_definition::WindowName, AnchorPoint, WindowStacking}, config::{window_definition::WindowName, AnchorPoint, WindowStacking},
display_backend, eww_state, display_backend, eww_state,
script_var_handler::*, script_var_handler::*,
value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName}, value::{Coords, NumWithUnit, PrimitiveValue, VarName},
widgets,
}; };
use anyhow::*; use anyhow::*;
use debug_stub_derive::*; use debug_stub_derive::*;
@ -67,7 +66,7 @@ pub enum DaemonCommand {
PrintWindows(DaemonResponseSender), PrintWindows(DaemonResponseSender),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub struct EwwWindow { pub struct EwwWindow {
pub name: WindowName, pub name: WindowName,
pub definition: config::EwwWindowDefinition, pub definition: config::EwwWindowDefinition,
@ -105,13 +104,14 @@ impl App {
DaemonCommand::NoOp => {} DaemonCommand::NoOp => {}
DaemonCommand::UpdateVars(mappings) => { DaemonCommand::UpdateVars(mappings) => {
for (var_name, new_value) in mappings { for (var_name, new_value) in mappings {
self.update_state(var_name, new_value)?; self.update_state(var_name, new_value);
} }
} }
DaemonCommand::ReloadConfigAndCss(sender) => { DaemonCommand::ReloadConfigAndCss(sender) => {
let mut errors = Vec::new(); let mut errors = Vec::new();
let config_result = config::EwwConfig::read_from_file(&self.config_file_path); let config_result =
config::RawEwwConfig::read_from_file(&self.config_file_path).and_then(config::EwwConfig::generate);
match config_result { match config_result {
Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)), Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)),
Err(e) => errors.push(e), Err(e) => errors.push(e),
@ -211,7 +211,7 @@ impl App {
gtk::main_quit(); gtk::main_quit();
} }
fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> { fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) {
self.eww_state.update_variable(fieldname, value) self.eww_state.update_variable(fieldname, value)
} }
@ -247,13 +247,7 @@ impl App {
let mut window_def = self.eww_config.get_window(window_name)?.clone(); let mut window_def = self.eww_config.get_window(window_name)?.clone();
window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size); window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size);
let root_widget = widgets::widget_use_to_gtk_widget( let root_widget = window_def.widget.render(&mut self.eww_state, window_name)?;
&self.eww_config.get_widgets(),
&mut self.eww_state,
window_name,
&maplit::hashmap! { "window_name".into() => AttrValue::from_primitive(window_name.to_string()) },
&window_def.widget,
)?;
root_widget.get_style_context().add_class(&window_name.to_string()); root_widget.get_style_context().add_class(&window_name.to_string());
let monitor_geometry = get_monitor_geometry(window_def.screen_number.unwrap_or_else(get_default_monitor_index)); let monitor_geometry = get_monitor_geometry(window_def.screen_number.unwrap_or_else(get_default_monitor_index));

View file

@ -35,11 +35,6 @@ impl WidgetDefinition {
} }
} }
} }
/// returns all the variables that are referenced in this widget
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
self.structure.referenced_vars()
}
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -106,17 +101,6 @@ impl WidgetUse {
self.text_pos = Some(text_pos); self.text_pos = Some(text_pos);
self self
} }
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
self.attrs
.get(key)
.context(format!("attribute '{}' missing from widgetuse of '{}'", key, &self.name))
}
/// returns all the variables that are referenced in this widget
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -9,22 +9,87 @@ use crate::{
use super::{ use super::{
element::WidgetDefinition, element::WidgetDefinition,
xml_ext::{XmlElement, XmlNode}, xml_ext::{XmlElement, XmlNode},
EwwWindowDefinition, ScriptVar, WindowName, EwwWindowDefinition, RawEwwWindowDefinition, ScriptVar, WindowName,
}; };
use std::path::PathBuf; use std::path::PathBuf;
/// Eww configuration structure.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Structure to hold the eww config
pub struct EwwConfig { pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, EwwWindowDefinition>, windows: HashMap<WindowName, EwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimitiveValue>, initial_variables: HashMap<VarName, PrimitiveValue>,
script_vars: HashMap<VarName, ScriptVar>, script_vars: HashMap<VarName, ScriptVar>,
pub filepath: PathBuf, pub filepath: PathBuf,
} }
impl EwwConfig { impl EwwConfig {
pub fn merge_includes(mut eww_config: EwwConfig, includes: Vec<EwwConfig>) -> Result<EwwConfig> { pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
Ok(Self::generate(RawEwwConfig::read_from_file(path)?)?)
}
pub fn generate(conf: RawEwwConfig) -> Result<Self> {
let RawEwwConfig {
windows,
initial_variables,
script_vars,
filepath,
widgets,
} = conf;
Ok(EwwConfig {
initial_variables,
script_vars,
filepath,
windows: windows
.into_iter()
.map(|(name, window)| {
Ok((
name,
EwwWindowDefinition::generate(&widgets, window).context("Failed expand window definition")?,
))
})
.collect::<Result<HashMap<_, _>>>()?,
})
}
// TODO this is kinda ugly
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, PrimitiveValue>> {
let mut vars = self
.script_vars
.iter()
.map(|var| Ok((var.0.clone(), var.1.initial_value()?)))
.collect::<Result<HashMap<_, _>>>()?;
vars.extend(self.initial_variables.clone());
Ok(vars)
}
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
&self.windows
}
pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> {
self.windows
.get(name)
.with_context(|| format!("No window named '{}' exists", name))
}
pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVar> {
self.script_vars
.get(name)
.with_context(|| format!("No script var named '{}' exists", name))
}
}
/// Raw Eww configuration, before expanding widget usages.
#[derive(Debug, Clone)]
pub struct RawEwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, RawEwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimitiveValue>,
script_vars: HashMap<VarName, ScriptVar>,
pub filepath: PathBuf,
}
impl RawEwwConfig {
pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec<RawEwwConfig>) -> Result<RawEwwConfig> {
let config_path = eww_config.filepath.clone(); let config_path = eww_config.filepath.clone();
let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| { let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
eprintln!( eprintln!(
@ -37,16 +102,16 @@ impl EwwConfig {
}; };
for included_config in includes { for included_config in includes {
for conflict in extend_safe(&mut eww_config.widgets, included_config.widgets) { for conflict in util::extend_safe(&mut eww_config.widgets, included_config.widgets) {
log_conflict("widget", &conflict, &included_config.filepath) log_conflict("widget", &conflict, &included_config.filepath)
} }
for conflict in extend_safe(&mut eww_config.windows, included_config.windows) { for conflict in util::extend_safe(&mut eww_config.windows, included_config.windows) {
log_conflict("window", &conflict.to_string(), &included_config.filepath) log_conflict("window", &conflict.to_string(), &included_config.filepath)
} }
for conflict in extend_safe(&mut eww_config.script_vars, included_config.script_vars) { for conflict in util::extend_safe(&mut eww_config.script_vars, included_config.script_vars) {
log_conflict("script-var", &conflict.to_string(), &included_config.filepath) log_conflict("script-var", &conflict.to_string(), &included_config.filepath)
} }
for conflict in extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) { for conflict in util::extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) {
log_conflict("var", &conflict.to_string(), &included_config.filepath) log_conflict("var", &conflict.to_string(), &included_config.filepath)
} }
} }
@ -60,16 +125,16 @@ impl EwwConfig {
let root_node = XmlNode::from(document.root_element()); let root_node = XmlNode::from(document.root_element());
let root_element = root_node.as_element()?; let root_element = root_node.as_element()?;
let (config, included_paths) = EwwConfig::from_xml_element(root_element.clone(), path.as_ref()) let (config, included_paths) = Self::from_xml_element(root_element.clone(), path.as_ref())
.with_context(|| format!("Error parsing eww config file {}", path.as_ref().display()))?; .with_context(|| format!("Error parsing eww config file {}", path.as_ref().display()))?;
let parsed_includes = included_paths let parsed_includes = included_paths
.into_iter() .into_iter()
.map(|included_path| EwwConfig::read_from_file(included_path)) .map(|included_path| Self::read_from_file(included_path))
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
.with_context(|| format!("Included in {}", path.as_ref().display()))?; .with_context(|| format!("Included in {}", path.as_ref().display()))?;
EwwConfig::merge_includes(config, parsed_includes) Self::merge_includes(config, parsed_includes)
.context("Failed to merge included files into parent configuration file")? .context("Failed to merge included files into parent configuration file")?
}; };
result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display())) result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display()))
@ -83,7 +148,7 @@ impl EwwConfig {
.child_elements() .child_elements()
.map(|child| { .map(|child| {
crate::ensure_xml_tag_is!(child, "file"); crate::ensure_xml_tag_is!(child, "file");
Ok(join_path_pretty(path, PathBuf::from(child.attr("path")?))) Ok(util::join_path_pretty(path, PathBuf::from(child.attr("path")?)))
}) })
.collect::<Result<Vec<_>>>()?, .collect::<Result<Vec<_>>>()?,
None => Default::default(), None => Default::default(),
@ -106,7 +171,7 @@ impl EwwConfig {
Some(tag) => tag Some(tag) => tag
.child_elements() .child_elements()
.map(|child| { .map(|child| {
let def = EwwWindowDefinition::from_xml_element(&child).with_context(|| { let def = RawEwwWindowDefinition::from_xml_element(&child).with_context(|| {
format!("Error parsing window definition at {}:{}", path.display(), &child.text_pos()) format!("Error parsing window definition at {}:{}", path.display(), &child.text_pos())
})?; })?;
Ok((def.name.to_owned(), def)) Ok((def.name.to_owned(), def))
@ -120,7 +185,7 @@ impl EwwConfig {
None => Default::default(), None => Default::default(),
}; };
let config = EwwConfig { let config = RawEwwConfig {
widgets: definitions, widgets: definitions,
windows, windows,
initial_variables, initial_variables,
@ -129,45 +194,6 @@ impl EwwConfig {
}; };
Ok((config, included_paths)) Ok((config, included_paths))
} }
// TODO this is kinda ugly
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, PrimitiveValue>> {
let mut vars = self
.script_vars
.iter()
.map(|var| Ok((var.0.clone(), var.1.initial_value()?)))
.collect::<Result<HashMap<_, _>>>()?;
vars.extend(self.get_default_vars().clone());
Ok(vars)
}
pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets
}
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
&self.windows
}
pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> {
self.windows
.get(name)
.with_context(|| format!("No window named '{}' exists", name))
}
pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
&self.initial_variables
}
pub fn get_script_vars(&self) -> Vec<ScriptVar> {
self.script_vars.values().cloned().collect()
}
pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVar> {
self.script_vars
.get(name)
.with_context(|| format!("No script var named '{}' exists", name))
}
} }
fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveValue>, HashMap<VarName, ScriptVar>)> { fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveValue>, HashMap<VarName, ScriptVar>)> {
@ -176,12 +202,11 @@ fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveV
for node in xml.child_elements() { for node in xml.child_elements() {
match node.tag_name() { match node.tag_name() {
"var" => { "var" => {
let var_name = VarName(node.attr("name")?.to_owned());
let value = node let value = node
.only_child() .only_child()
.map(|c| c.as_text_or_sourcecode()) .map(|c| c.as_text_or_sourcecode())
.unwrap_or_else(|_| String::new()); .unwrap_or_else(|_| String::new());
normal_vars.insert(var_name, PrimitiveValue::from_string(value)); normal_vars.insert(VarName(node.attr("name")?.to_owned()), PrimitiveValue::from_string(value));
} }
"script-var" => { "script-var" => {
let script_var = ScriptVar::from_xml_element(node)?; let script_var = ScriptVar::from_xml_element(node)?;
@ -193,33 +218,9 @@ fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveV
Ok((normal_vars, script_vars)) Ok((normal_vars, script_vars))
} }
/// Joins two paths while keeping it somewhat pretty.
/// If the second path is absolute, this will just return the second path.
/// If it is relative, it will return the second path joined onto the first path, removing any `./` if present.
/// TODO this is not yet perfect, as it will still leave ../ and multiple ./ etc,... check for a Path::simplify or something.
fn join_path_pretty<P: AsRef<std::path::Path>, P2: AsRef<std::path::Path>>(a: P, b: P2) -> PathBuf {
let a = a.as_ref();
let b = b.as_ref();
if b.is_absolute() {
b.to_path_buf()
} else {
a.parent().unwrap().join(b.strip_prefix("./").unwrap_or(&b))
}
}
/// extends a hashmap, returning a list of keys that already where present in the hashmap.
fn extend_safe<K: std::cmp::Eq + std::hash::Hash + Clone, V, T: IntoIterator<Item = (K, V)>>(
a: &mut HashMap<K, V>,
b: T,
) -> Vec<K> {
b.into_iter()
.filter_map(|(k, v)| a.insert(k.clone(), v).map(|_| k.clone()))
.collect()
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::config::{EwwConfig, XmlNode}; use crate::config::{RawEwwConfig, XmlNode};
use std::collections::HashMap; use std::collections::HashMap;
#[test] #[test]
@ -274,13 +275,13 @@ mod test {
let document1 = roxmltree::Document::parse(&input1).unwrap(); let document1 = roxmltree::Document::parse(&input1).unwrap();
let document2 = roxmltree::Document::parse(input2).unwrap(); let document2 = roxmltree::Document::parse(input2).unwrap();
let config1 = EwwConfig::from_xml_element(XmlNode::from(document1.root_element()).as_element().unwrap().clone(), "") let config1 = RawEwwConfig::from_xml_element(XmlNode::from(document1.root_element()).as_element().unwrap().clone(), "")
.unwrap() .unwrap()
.0; .0;
let config2 = EwwConfig::from_xml_element(XmlNode::from(document2.root_element()).as_element().unwrap().clone(), "") let config2 = RawEwwConfig::from_xml_element(XmlNode::from(document2.root_element()).as_element().unwrap().clone(), "")
.unwrap() .unwrap()
.0; .0;
let base_config = EwwConfig { let base_config = RawEwwConfig {
widgets: HashMap::new(), widgets: HashMap::new(),
windows: HashMap::new(), windows: HashMap::new(),
initial_variables: HashMap::new(), initial_variables: HashMap::new(),
@ -288,7 +289,7 @@ mod test {
filepath: "test_path".into(), filepath: "test_path".into(),
}; };
let merged_config = EwwConfig::merge_includes(base_config, vec![config1, config2]).unwrap(); let merged_config = RawEwwConfig::merge_includes(base_config, vec![config1, config2]).unwrap();
assert_eq!(merged_config.widgets.len(), 2); assert_eq!(merged_config.widgets.len(), 2);
assert_eq!(merged_config.windows.len(), 2); assert_eq!(merged_config.windows.len(), 2);

View file

@ -41,7 +41,9 @@ impl ScriptVar {
pub fn initial_value(&self) -> Result<PrimitiveValue> { pub fn initial_value(&self) -> Result<PrimitiveValue> {
match self { match self {
ScriptVar::Poll(x) => Ok(run_command(&x.command)?), ScriptVar::Poll(x) => {
run_command(&x.command).with_context(|| format!("Failed to compute initial value for {}", &self.name()))
}
ScriptVar::Tail(_) => Ok(PrimitiveValue::from_string(String::new())), ScriptVar::Tail(_) => Ok(PrimitiveValue::from_string(String::new())),
} }
} }

View file

@ -1,28 +1,42 @@
use crate::{ensure_xml_tag_is, value::NumWithUnit}; use crate::{ensure_xml_tag_is, value::NumWithUnit, widgets::widget_node};
use anyhow::*; use anyhow::*;
use derive_more::*; use derive_more::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smart_default::SmartDefault; use smart_default::SmartDefault;
use std::collections::HashMap;
use super::*; use super::*;
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)] /// Full window-definition containing the fully expanded widget tree.
pub enum Side { /// **Use this** rather than `[RawEwwWindowDefinition]`.
#[default] #[derive(Debug, Clone)]
Top,
Left,
Right,
Bottom,
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
pub struct StrutDefinition {
pub side: Side,
pub dist: NumWithUnit,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EwwWindowDefinition { pub struct EwwWindowDefinition {
pub name: WindowName,
pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking,
pub screen_number: Option<i32>,
pub widget: Box<dyn widget_node::WidgetNode>,
pub struts: StrutDefinition,
pub focusable: bool,
}
impl EwwWindowDefinition {
pub fn generate(defs: &HashMap<String, WidgetDefinition>, window: RawEwwWindowDefinition) -> Result<Self> {
Ok(EwwWindowDefinition {
name: window.name,
geometry: window.geometry,
stacking: window.stacking,
screen_number: window.screen_number,
widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?,
struts: window.struts,
focusable: window.focusable,
})
}
}
/// Window-definition storing the raw WidgetUse, as received directly from parsing.
#[derive(Debug, Clone, PartialEq)]
pub struct RawEwwWindowDefinition {
pub name: WindowName, pub name: WindowName,
pub geometry: EwwWindowGeometry, pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking, pub stacking: WindowStacking,
@ -32,7 +46,7 @@ pub struct EwwWindowDefinition {
pub focusable: bool, pub focusable: bool,
} }
impl EwwWindowDefinition { impl RawEwwWindowDefinition {
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> { pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
ensure_xml_tag_is!(xml, "window"); ensure_xml_tag_is!(xml, "window");
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default(); let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
@ -44,11 +58,11 @@ impl EwwWindowDefinition {
let struts: Option<StrutDefinition> = xml let struts: Option<StrutDefinition> = xml
.child("reserve") .child("reserve")
.ok() .ok()
.map(parse_strut_definition) .map(StrutDefinition::from_xml_element)
.transpose() .transpose()
.context("Failed to parse <reserve>")?; .context("Failed to parse <reserve>")?;
Ok(EwwWindowDefinition { Ok(RawEwwWindowDefinition {
name: WindowName(xml.attr("name")?.to_owned()), name: WindowName(xml.attr("name")?.to_owned()),
geometry: match xml.child("geometry") { geometry: match xml.child("geometry") {
Ok(node) => EwwWindowGeometry::from_xml_element(node)?, Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
@ -61,30 +75,45 @@ impl EwwWindowDefinition {
struts: struts.unwrap_or_default(), struts: struts.unwrap_or_default(),
}) })
} }
}
/// returns all the variables that are referenced in this window #[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> { pub enum Side {
self.widget.referenced_vars() #[default]
Top,
Left,
Right,
Bottom,
}
impl std::str::FromStr for Side {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Side> {
match s {
"l" | "left" => Ok(Side::Left),
"r" | "right" => Ok(Side::Right),
"t" | "top" => Ok(Side::Top),
"b" | "bottom" => Ok(Side::Bottom),
_ => Err(anyhow!(
"Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"",
s
)),
}
} }
} }
fn parse_strut_definition(xml: XmlElement) -> Result<StrutDefinition> { #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
Ok(StrutDefinition { pub struct StrutDefinition {
side: parse_side(xml.attr("side")?)?, pub side: Side,
dist: xml.attr("distance")?.parse()?, pub dist: NumWithUnit,
})
} }
fn parse_side(s: &str) -> Result<Side> { impl StrutDefinition {
match s { pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
"l" | "left" => Ok(Side::Left), Ok(StrutDefinition {
"r" | "right" => Ok(Side::Right), side: xml.attr("side")?.parse()?,
"t" | "top" => Ok(Side::Top), dist: xml.attr("distance")?.parse()?,
"b" | "bottom" => Ok(Side::Bottom), })
_ => Err(anyhow!(
"Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"",
s
)),
} }
} }

View file

@ -7,11 +7,11 @@ use std::{collections::HashMap, sync::Arc};
use crate::value::{AttrValue, PrimitiveValue}; use crate::value::{AttrValue, PrimitiveValue};
/// Handler that get's executed to apply the necessary parts of the eww state to /// Handler that gets executed to apply the necessary parts of the eww state to
/// a gtk widget. These are created and initialized in EwwState::resolve. /// a gtk widget. These are created and initialized in EwwState::resolve.
pub struct StateChangeHandler { pub struct StateChangeHandler {
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>, func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
unresolved_values: Vec<(AttrName, AttrValue)>, unresolved_values: HashMap<AttrName, AttrValue>,
} }
impl StateChangeHandler { impl StateChangeHandler {
@ -24,19 +24,16 @@ impl StateChangeHandler {
fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) { fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) {
let resolved_attrs = self let resolved_attrs = self
.unresolved_values .unresolved_values
.iter() .clone()
.cloned() .into_iter()
.map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?))) .map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?)))
.collect::<Result<_>>(); .collect::<Result<_>>();
match resolved_attrs { match resolved_attrs {
Ok(resolved_attrs) => { Ok(resolved_attrs) => {
let result: Result<_> = (self.func)(resolved_attrs); crate::print_result_err!("while updating UI based after state change", &(self.func)(resolved_attrs))
crate::print_result_err!("while updating UI based after state change", &result);
}
Err(err) => {
eprintln!("Error while resolving attributes: {:?}", err);
} }
Err(err) => eprintln!("Error while resolving attributes: {:?}", err),
} }
} }
} }
@ -100,73 +97,56 @@ impl EwwState {
/// Update the value of a variable, running all registered /// Update the value of a variable, running all registered
/// [StateChangeHandler]s. /// [StateChangeHandler]s.
pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> { pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) {
self.variables_state.insert(key.clone(), value); self.variables_state.insert(key.clone(), value);
let handlers = self // run all of the handlers
.windows self.windows
.values() .values()
.filter_map(|window_state| window_state.state_change_handlers.get(&key)) .filter_map(|window_state| window_state.state_change_handlers.get(&key))
.flatten(); .flatten()
.for_each(|handler| handler.run_with_state(&self.variables_state));
for handler in handlers {
handler.run_with_state(&self.variables_state)
}
Ok(())
} }
/// resolves a value if possible, using the current eww_state /// Look up a single variable in the eww state, returning an `Err` when the value is not found.
/// Expects there to be at max one level of nesting var_refs from local-env. pub fn lookup(&self, var_name: &VarName) -> Result<&PrimitiveValue> {
/// This means that no elements in the local_env may be var-refs into the self.variables_state
/// local_env again, but only into the global state. .get(var_name)
pub fn resolve_once<'a>( .with_context(|| format!("Unknown variable '{}' referenced", var_name))
&'a self, }
local_env: &'a HashMap<VarName, AttrValue>,
value: &'a AttrValue, /// resolves a value if possible, using the current eww_state.
) -> Result<PrimitiveValue> { pub fn resolve_once<'a>(&'a self, value: &'a AttrValue) -> Result<PrimitiveValue> {
value value
.iter() .iter()
.map(|element| match element { .map(|element| match element {
AttrValueElement::Primitive(primitive) => Ok(primitive.clone()), AttrValueElement::Primitive(primitive) => Ok(primitive.clone()),
AttrValueElement::VarRef(var_name) => self AttrValueElement::VarRef(var_name) => self.lookup(var_name).cloned(),
.variables_state
.get(var_name)
.cloned()
.or_else(|| local_env.get(var_name).and_then(|x| self.resolve_once(local_env, x).ok()))
.with_context(|| format!("Unknown variable '{}' referenced", var_name)),
}) })
.collect() .collect()
} }
/// Resolve takes a function that applies a set of fully resolved attribute /// Resolve takes a function that applies a set of fully resolved attribute
/// values to it's gtk widget. Expects there to be at max one level of /// values to it's gtk widget.
/// nesting var_refs from local-env. This means that no elements in the
/// local_env may be var-refs into the local_env again, but only into the
/// global state.
pub fn resolve<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>( pub fn resolve<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>(
&mut self, &mut self,
window_name: &WindowName, window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>,
required_attributes: HashMap<AttrName, AttrValue>, required_attributes: HashMap<AttrName, AttrValue>,
set_value: F, set_value: F,
) { ) {
let handler = StateChangeHandler { let handler = StateChangeHandler {
func: Box::new(set_value), func: Box::new(set_value),
unresolved_values: required_attributes unresolved_values: required_attributes,
.into_iter()
.map(|(attr_name, attr_value)| (attr_name, attr_value.resolve_one_level(local_env)))
.collect(),
}; };
handler.run_with_state(&self.variables_state); handler.run_with_state(&self.variables_state);
// only store the handler if at least one variable is being used // only store the handler if at least one variable is being used
if handler.used_variables().next().is_some() { if handler.used_variables().next().is_some() {
let window_state = self self.windows
.windows
.entry(window_name.clone()) .entry(window_name.clone())
.or_insert_with(EwwWindowState::default); .or_insert_with(EwwWindowState::default)
window_state.put_handler(handler); .put_handler(handler);
} }
} }

View file

@ -50,6 +50,30 @@ macro_rules! loop_select {
} }
} }
/// Joins two paths while keeping it somewhat pretty.
/// If the second path is absolute, this will just return the second path.
/// If it is relative, it will return the second path joined onto the first path, removing any `./` if present.
/// TODO this is not yet perfect, as it will still leave ../ and multiple ./ etc,... check for a Path::simplify or something.
pub fn join_path_pretty<P: AsRef<std::path::Path>, P2: AsRef<std::path::Path>>(a: P, b: P2) -> std::path::PathBuf {
let a = a.as_ref();
let b = b.as_ref();
if b.is_absolute() {
b.to_path_buf()
} else {
a.parent().unwrap().join(b.strip_prefix("./").unwrap_or(&b))
}
}
/// extends a hashmap, returning a list of keys that already where present in the hashmap.
pub fn extend_safe<K: std::cmp::Eq + std::hash::Hash + Clone, V, T: IntoIterator<Item = (K, V)>>(
a: &mut std::collections::HashMap<K, V>,
b: T,
) -> Vec<K> {
b.into_iter()
.filter_map(|(k, v)| a.insert(k.clone(), v).map(|_| k.clone()))
.collect()
}
/// read an scss file, replace all environment variable references within it and /// read an scss file, replace all environment variable references within it and
/// then parse it into css. /// then parse it into css.
pub fn parse_scss_from_file(path: &Path) -> Result<String> { pub fn parse_scss_from_file(path: &Path) -> Result<String> {

View file

@ -43,6 +43,9 @@ impl AttrValue {
self.0.iter().filter_map(|x| x.as_var_ref()) self.0.iter().filter_map(|x| x.as_var_ref())
} }
/// resolve partially.
/// If a var-ref links to another var-ref, that other var-ref is used.
/// If a referenced variable is not found in the given hashmap, returns the var-ref unchanged.
pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> AttrValue { pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> AttrValue {
self.into_iter() self.into_iter()
.flat_map(|entry| match entry { .flat_map(|entry| match entry {
@ -55,6 +58,9 @@ impl AttrValue {
.collect() .collect()
} }
/// resolve fully.
/// As the variables here have to be primitive values,
/// this enforces that var-refs are not linking to other variables.
pub fn resolve_fully(self, variables: &HashMap<VarName, PrimitiveValue>) -> Result<PrimitiveValue> { pub fn resolve_fully(self, variables: &HashMap<VarName, PrimitiveValue>) -> Result<PrimitiveValue> {
self.into_iter() self.into_iter()
.map(|element| match element { .map(|element| match element {

View file

@ -1,17 +1,13 @@
use crate::{ use crate::{config::window_definition::WindowName, eww_state::*, print_result_err, value::AttrName};
config::{element, window_definition::WindowName},
eww_state::*,
print_result_err,
value::{AttrName, AttrValue, VarName},
};
use anyhow::*; use anyhow::*;
use gtk::prelude::*; use gtk::prelude::*;
use itertools::Itertools; use itertools::Itertools;
use std::{collections::HashMap, process::Command}; use std::process::Command;
use widget_definitions::*; use widget_definitions::*;
pub mod widget_definitions; pub mod widget_definitions;
pub mod widget_node;
const CMD_STRING_PLACEHODLER: &str = "{}"; const CMD_STRING_PLACEHODLER: &str = "{}";
@ -27,71 +23,11 @@ pub(self) fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
print_result_err!(format!("executing command {}", &cmd), command_result); print_result_err!(format!("executing command {}", &cmd), command_result);
} }
struct BuilderArgs<'a, 'b, 'c, 'd, 'e> { struct BuilderArgs<'a, 'b, 'c, 'd> {
eww_state: &'a mut EwwState, eww_state: &'a mut EwwState,
local_env: &'b HashMap<VarName, AttrValue>, widget: &'b widget_node::Generic,
widget: &'c element::WidgetUse,
unhandled_attrs: Vec<&'c AttrName>, unhandled_attrs: Vec<&'c AttrName>,
window_name: &'d WindowName, window_name: &'d WindowName,
widget_definitions: &'e HashMap<String, element::WidgetDefinition>,
}
/// Generate a [gtk::Widget] from a [element::WidgetUse].
/// The widget_use may be using a builtin widget, or a custom
/// [element::WidgetDefinition].
///
/// Also registers all the necessary state-change handlers in the eww_state.
///
/// This may return `Err` in case there was an actual error while parsing or
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
pub fn widget_use_to_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState,
window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>,
widget: &element::WidgetUse,
) -> Result<gtk::Widget> {
let builtin_gtk_widget = build_builtin_gtk_widget(widget_definitions, eww_state, window_name, local_env, widget)?;
let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget {
builtin_gtk_widget
} else if let Some(def) = widget_definitions.get(widget.name.as_str()) {
// let mut local_env = local_env.clone();
// the attributes that are set on the widget need to be resolved as far as
// possible. If an attribute is a variable reference, it must either reference a
// variable in the current local_env, or in the global state. As we are building
// widgets from the outer most to the most nested, we can resolve attributes at
// every step. This way, any definition that is affected by changes in the
// eww_state will be directly linked to the eww_state's value. Example:
// foo="{{in_eww_state}}" => attr_in_child="{{foo}}" =>
// attr_in_nested_child="{{attr_in_child}}" will be resolved step by step. This
// code will first resolve attr_in_child to directly be
// attr_in_child={{in_eww_state}}. then, in the widget_use_to_gtk_widget call of
// that child element, attr_in_nested_child will again be resolved to point to
// the value of attr_in_child, and thus: attr_in_nested_child="{{in_eww_state}}"
let resolved_widget_attr_env = widget
.attrs
.clone()
.into_iter()
.map(|(attr_name, attr_value)| (VarName(attr_name.0), attr_value.resolve_one_level(local_env)))
.collect();
let custom_widget = widget_use_to_gtk_widget(
widget_definitions,
eww_state,
window_name,
&resolved_widget_attr_env,
&def.structure,
)?;
custom_widget.get_style_context().add_class(widget.name.as_str());
custom_widget
} else {
bail!("unknown widget: '{}'", &widget.name);
};
Ok(gtk_widget)
} }
/// build a [`gtk::Widget`] out of a [`element::WidgetUse`] that uses a /// build a [`gtk::Widget`] out of a [`element::WidgetUse`] that uses a
@ -104,19 +40,15 @@ pub fn widget_use_to_gtk_widget(
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any /// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name. /// widget name.
fn build_builtin_gtk_widget( fn build_builtin_gtk_widget(
widget_definitions: &HashMap<String, element::WidgetDefinition>,
eww_state: &mut EwwState, eww_state: &mut EwwState,
window_name: &WindowName, window_name: &WindowName,
local_env: &HashMap<VarName, AttrValue>, widget: &widget_node::Generic,
widget: &element::WidgetUse,
) -> Result<Option<gtk::Widget>> { ) -> Result<Option<gtk::Widget>> {
let mut bargs = BuilderArgs { let mut bargs = BuilderArgs {
eww_state, eww_state,
local_env,
widget, widget,
window_name, window_name,
unhandled_attrs: widget.attrs.keys().collect(), unhandled_attrs: widget.attrs.keys().collect(),
widget_definitions,
}; };
let gtk_widget = match widget_to_gtk_widget(&mut bargs) { let gtk_widget = match widget_to_gtk_widget(&mut bargs) {
Ok(Some(gtk_widget)) => gtk_widget, Ok(Some(gtk_widget)) => gtk_widget,
@ -136,8 +68,7 @@ fn build_builtin_gtk_widget(
if let Some(gtk_widget) = gtk_widget.dynamic_cast_ref::<gtk::Container>() { if let Some(gtk_widget) = gtk_widget.dynamic_cast_ref::<gtk::Container>() {
resolve_container_attrs(&mut bargs, &gtk_widget); resolve_container_attrs(&mut bargs, &gtk_widget);
for child in &widget.children { for child in &widget.children {
let child_widget = widget_use_to_gtk_widget(widget_definitions, bargs.eww_state, window_name, local_env, child); let child_widget = child.render(bargs.eww_state, window_name).with_context(|| {
let child_widget = child_widget.with_context(|| {
format!( format!(
"{}error while building child '{:#?}' of '{}'", "{}error while building child '{:#?}' of '{}'",
widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(), widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
@ -193,7 +124,6 @@ macro_rules! resolve_block {
if let Ok(attr_map) = attr_map { if let Ok(attr_map) = attr_map {
$args.eww_state.resolve( $args.eww_state.resolve(
$args.window_name, $args.window_name,
$args.local_env,
attr_map, attr_map,
::glib::clone!(@strong $gtk_widget => move |attrs| { ::glib::clone!(@strong $gtk_widget => move |attrs| {
$( $(

View file

@ -1,8 +1,8 @@
use super::{run_command, BuilderArgs}; use super::{run_command, BuilderArgs};
use crate::{config, eww_state, resolve_block, value::AttrValue}; use crate::{config, eww_state, resolve_block, value::AttrValue, widgets::widget_node};
use anyhow::*; use anyhow::*;
use gtk::{prelude::*, ImageExt}; use gtk::{prelude::*, ImageExt};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, collections::HashMap, rc::Rc};
// TODO figure out how to // TODO figure out how to
// TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html // TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html
@ -40,7 +40,7 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: &gtk::Wi
if let Ok(visible) = bargs if let Ok(visible) = bargs
.widget .widget
.get_attr("visible") .get_attr("visible")
.and_then(|v| bargs.eww_state.resolve_once(bargs.local_env, v)?.as_bool()) .and_then(|v| bargs.eww_state.resolve_once(v)?.as_bool())
{ {
connect_first_map(gtk_widget, move |w| { connect_first_map(gtk_widget, move |w| {
if visible { if visible {
@ -410,7 +410,6 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
// TODO these clones here are dumdum // TODO these clones here are dumdum
let window_name = bargs.window_name.clone(); let window_name = bargs.window_name.clone();
let widget_definitions = bargs.widget_definitions.clone();
resolve_block!(bargs, gtk_widget, { resolve_block!(bargs, gtk_widget, {
// @prop content - inline Eww XML that will be rendered as a widget. // @prop content - inline Eww XML that will be rendered as a widget.
prop(content: as_string) { prop(content: as_string) {
@ -418,13 +417,9 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
if !content.is_empty() { if !content.is_empty() {
let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!("Failed to parse eww xml literal: {:?}", e))?; let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!("Failed to parse eww xml literal: {:?}", e))?;
let content_widget_use = config::element::WidgetUse::from_xml_node(document.root_element().into())?; let content_widget_use = config::element::WidgetUse::from_xml_node(document.root_element().into())?;
let child_widget = super::widget_use_to_gtk_widget(
&widget_definitions, let widget_node = &*widget_node::generate_generic_widget_node(&HashMap::new(), &HashMap::new(), content_widget_use)?;
&mut eww_state::EwwState::default(), let child_widget = widget_node.render( &mut eww_state::EwwState::default(), &window_name)?;
&window_name,
&std::collections::HashMap::new(),
&content_widget_use,
)?;
gtk_widget.add(&child_widget); gtk_widget.add(&child_widget);
child_widget.show(); child_widget.show();
} }

162
src/widgets/widget_node.rs Normal file
View file

@ -0,0 +1,162 @@
use crate::{
config::{
element::{WidgetDefinition, WidgetUse},
WindowName,
},
eww_state::EwwState,
value::{AttrName, AttrValue, VarName},
};
use anyhow::*;
use dyn_clone;
use std::collections::HashMap;
pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync {
fn get_name(&self) -> &str;
fn get_text_pos(&self) -> Option<&roxmltree::TextPos>;
/// Generate a [gtk::Widget] from a [element::WidgetUse].
///
/// Also registers all the necessary state-change handlers in the eww_state.
///
/// This may return `Err` in case there was an actual error while parsing or
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
/// widget name.
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget>;
}
dyn_clone::clone_trait_object!(WidgetNode);
#[derive(Debug, Clone)]
pub struct UserDefined {
name: String,
text_pos: Option<roxmltree::TextPos>,
content: Box<dyn WidgetNode>,
}
impl WidgetNode for UserDefined {
fn get_name(&self) -> &str {
&self.name
}
fn get_text_pos(&self) -> Option<&roxmltree::TextPos> {
self.text_pos.as_ref()
}
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget> {
self.content.render(eww_state, window_name)
}
}
#[derive(Debug, Clone)]
pub struct Generic {
pub name: String,
pub text_pos: Option<roxmltree::TextPos>,
pub children: Vec<Box<dyn WidgetNode>>,
pub attrs: HashMap<AttrName, AttrValue>,
}
impl Generic {
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
self.attrs
.get(key)
.context(format!("attribute '{}' missing from use of '{}'", key, &self.name))
}
/// returns all the variables that are referenced in this widget
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
}
}
impl WidgetNode for Generic {
fn get_name(&self) -> &str {
&self.name
}
fn get_text_pos(&self) -> Option<&roxmltree::TextPos> {
self.text_pos.as_ref()
}
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget> {
Ok(crate::widgets::build_builtin_gtk_widget(eww_state, window_name, &self)?
.with_context(|| format!("Unknown widget '{}'", self.get_name()))?)
}
}
pub fn generate_generic_widget_node(
defs: &HashMap<String, WidgetDefinition>,
local_env: &HashMap<VarName, AttrValue>,
w: WidgetUse,
) -> Result<Box<dyn WidgetNode>> {
if let Some(def) = defs.get(&w.name) {
ensure!(w.children.is_empty(), "User-defined widgets cannot be given children.");
let new_local_env = w
.attrs
.into_iter()
.map(|(name, value)| (VarName(name.0), value.resolve_one_level(local_env)))
.collect::<HashMap<_, _>>();
let content = generate_generic_widget_node(defs, &new_local_env, def.structure.clone())?;
Ok(Box::new(UserDefined {
name: w.name,
text_pos: w.text_pos,
content,
}))
} else {
Ok(Box::new(Generic {
name: w.name,
text_pos: w.text_pos,
attrs: w
.attrs
.into_iter()
.map(|(name, value)| (name, value.resolve_one_level(local_env)))
.collect(),
children: w
.children
.into_iter()
.map(|child| generate_generic_widget_node(defs, local_env, child))
.collect::<Result<Vec<_>>>()?,
}))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::config::xml_ext::*;
use maplit::hashmap;
use pretty_assertions::assert_eq;
#[test]
fn test_generic_generate() {
let w_def1 = {
let input = r#"<def name="foo"><box><box>{{nested1}}{{raw1}}</box></box></def>"#;
let document = roxmltree::Document::parse(input).unwrap();
let xml = XmlNode::from(document.root_element().clone());
WidgetDefinition::from_xml_element(&xml.as_element().unwrap()).unwrap()
};
let w_def2 = {
let input = r#"<def name="bar"><box><foo nested1="{{nested2}}" raw1="raw value"/></box></def>"#;
let document = roxmltree::Document::parse(input).unwrap();
let xml = XmlNode::from(document.root_element().clone());
WidgetDefinition::from_xml_element(&xml.as_element().unwrap()).unwrap()
};
let w_use = {
let input = r#"<bar nested2="{{in_root}}"/>"#;
let document = roxmltree::Document::parse(input).unwrap();
let xml = XmlNode::from(document.root_element().clone());
WidgetUse::from_xml_node(xml).unwrap()
};
let generic = generate_generic_widget_node(
&hashmap! { "foo".to_string() => w_def1, "bar".to_string() => w_def2 },
&HashMap::new(),
w_use,
)
.unwrap();
// TODO actually implement this test ._.
dbg!(&generic);
// panic!("REEEEEEEEEE")
}
}