diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index cb57aa2..77456d6 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -1,9 +1,7 @@ use crate::{ - config, - config::{window_definition::WindowName, AnchorPoint}, + config::{self, EwwConfig}, display_backend, eww_state, script_var_handler::*, - dynval::{Coords, NumWithUnit, DynVal, VarName}, EwwPaths, }; use anyhow::*; @@ -11,8 +9,13 @@ use debug_stub_derive::*; use gdk::WindowExt; use gtk::{ContainerExt, CssProviderExt, GtkWindowExt, StyleContextExt, WidgetExt}; use itertools::Itertools; +use simplexpr::dynval::DynVal; use std::collections::HashMap; use tokio::sync::mpsc::UnboundedSender; +use yuck::{ + config::window_geometry::{AnchorPoint, WindowGeometry}, + value::{Coords, VarName}, +}; /// Response that the app may send as a response to a event. /// This is used in `DaemonCommand`s that contain a response sender. @@ -43,11 +46,11 @@ pub enum DaemonCommand { UpdateConfig(config::EwwConfig), UpdateCss(String), OpenMany { - windows: Vec, + windows: Vec, sender: DaemonResponseSender, }, OpenWindow { - window_name: WindowName, + window_name: String, pos: Option, size: Option, anchor: Option, @@ -55,7 +58,7 @@ pub enum DaemonCommand { sender: DaemonResponseSender, }, CloseWindow { - window_name: WindowName, + window_name: String, sender: DaemonResponseSender, }, KillServer, @@ -70,7 +73,7 @@ pub enum DaemonCommand { #[derive(Debug, Clone)] pub struct EwwWindow { - pub name: WindowName, + pub name: String, pub definition: config::EwwWindowDefinition, pub gtk_window: gtk::Window, } @@ -85,7 +88,7 @@ impl EwwWindow { pub struct App { pub eww_state: eww_state::EwwState, pub eww_config: config::EwwConfig, - pub open_windows: HashMap, + pub open_windows: HashMap, pub css_provider: gtk::CssProvider, #[debug_stub = "ScriptVarHandler(...)"] @@ -109,27 +112,30 @@ impl App { } } DaemonCommand::ReloadConfigAndCss(sender) => { - let mut errors = Vec::new(); - let config_result = config::RawEwwConfig::read_from_file(&self.paths.get_eww_xml_path()) - .and_then(config::EwwConfig::generate); - match config_result { - Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)), - Err(e) => errors.push(e), - } + // TODO implement this + //let mut errors = Vec::new(); + todo!() - let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path()); - match css_result { - Ok(new_css) => self.handle_command(DaemonCommand::UpdateCss(new_css)), - Err(e) => errors.push(e), - } + //let config_result = + //EwwConfig::read_from_file(&self.paths.get_eww_xml_path()).and_then(config::EwwConfig::generate); + //match config_result { + //Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)), + //Err(e) => errors.push(e), + //} - let errors = errors.into_iter().map(|e| format!("{:?}", e)).join("\n"); - if errors.is_empty() { - sender.send(DaemonResponse::Success(String::new()))?; - } else { - sender.send(DaemonResponse::Failure(errors))?; - } + //let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path()); + //match css_result { + //Ok(new_css) => self.handle_command(DaemonCommand::UpdateCss(new_css)), + //Err(e) => errors.push(e), + //} + + //let errors = errors.into_iter().map(|e| format!("{:?}", e)).join("\n"); + //if errors.is_empty() { + //sender.send(DaemonResponse::Success(String::new()))?; + //} else { + //sender.send(DaemonResponse::Failure(errors))?; + //} } DaemonCommand::UpdateConfig(config) => { self.load_config(config)?; @@ -203,7 +209,7 @@ impl App { self.eww_state.update_variable(fieldname, value) } - fn close_window(&mut self, window_name: &WindowName) -> Result<()> { + fn close_window(&mut self, window_name: &String) -> Result<()> { for unused_var in self.variables_only_used_in(window_name) { log::info!("stopping for {}", &unused_var); self.script_var_handler.stop_for_variable(unused_var.clone()); @@ -220,11 +226,11 @@ impl App { fn open_window( &mut self, - window_name: &WindowName, + window_name: &String, pos: Option, size: Option, monitor: Option, - anchor: Option, + anchor: Option, ) -> Result<()> { // remove and close existing window with the same name let _ = self.close_window(window_name); @@ -238,7 +244,7 @@ impl App { root_widget.get_style_context().add_class(&window_name.to_string()); let monitor_geometry = - get_monitor_geometry(monitor.or(window_def.screen_number).unwrap_or_else(get_default_monitor_index)); + get_monitor_geometry(monitor.or(window_def.monitor_number).unwrap_or_else(get_default_monitor_index)); let eww_window = initialize_window(monitor_geometry, root_widget, window_def)?; self.open_windows.insert(window_name.clone(), eww_window); @@ -281,7 +287,7 @@ impl App { } /// Get all variables mapped to a list of windows they are being used in. - pub fn currently_used_variables<'a>(&'a self) -> HashMap<&'a VarName, Vec<&'a WindowName>> { + pub fn currently_used_variables<'a>(&'a self) -> HashMap<&'a VarName, Vec<&'a String>> { let mut vars: HashMap<&'a VarName, Vec<_>> = HashMap::new(); for window_name in self.open_windows.keys() { for var in self.eww_state.vars_referenced_in(window_name) { @@ -292,7 +298,7 @@ impl App { } /// Get all variables that are only used in the given window. - pub fn variables_only_used_in<'a>(&'a self, window: &'a WindowName) -> impl Iterator { + pub fn variables_only_used_in<'a>(&'a self, window: &'a String) -> impl Iterator { self.currently_used_variables() .into_iter() .filter(move |(_, wins)| wins.len() == 1 && wins.contains(&window)) @@ -306,14 +312,14 @@ fn initialize_window( window_def: config::EwwWindowDefinition, ) -> Result { let window = display_backend::initialize_window(&window_def, monitor_geometry) - .with_context(|| format!("monitor {} is unavailable", window_def.screen_number.unwrap()))?; + .with_context(|| format!("monitor {} is unavailable", window_def.monitor_number.unwrap()))?; window.set_title(&format!("Eww - {}", window_def.name)); window.set_position(gtk::WindowPosition::None); window.set_gravity(gdk::Gravity::Center); if let Some(geometry) = window_def.geometry { - let actual_window_rect = geometry.get_window_rectangle(monitor_geometry); + let actual_window_rect = get_window_rectangle(geometry, monitor_geometry); window.set_size_request(actual_window_rect.width, actual_window_rect.height); window.set_default_size(actual_window_rect.width, actual_window_rect.height); } @@ -345,13 +351,13 @@ fn initialize_window( /// Apply the provided window-positioning rules to the window. fn apply_window_position( - mut window_geometry: config::EwwWindowGeometry, + mut window_geometry: WindowGeometry, monitor_geometry: gdk::Rectangle, window: >k::Window, ) -> Result<()> { let gdk_window = window.get_window().context("Failed to get gdk window from gtk window")?; window_geometry.size = Coords::from_pixels(window.get_size()); - let actual_window_rect = window_geometry.get_window_rectangle(monitor_geometry); + let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry); gdk_window.move_(actual_window_rect.x, actual_window_rect.y); Ok(()) } @@ -382,3 +388,11 @@ fn respond_with_error(sender: DaemonResponseSender, result: Result) -> Res } .context("sending response from main thread") } + +pub fn get_window_rectangle(geometry: WindowGeometry, screen_rect: gdk::Rectangle) -> gdk::Rectangle { + let (offset_x, offset_y) = geometry.offset.relative_to(screen_rect.width, screen_rect.height); + let (width, height) = geometry.size.relative_to(screen_rect.width, screen_rect.height); + let x = screen_rect.x + offset_x + geometry.anchor_point.x.alignment_to_coordinate(width, screen_rect.width); + let y = screen_rect.y + offset_y + geometry.anchor_point.y.alignment_to_coordinate(height, screen_rect.height); + gdk::Rectangle { x, y, width, height } +} diff --git a/crates/eww/src/config/backend_window_options.rs b/crates/eww/src/config/backend_window_options.rs deleted file mode 100644 index f194b3c..0000000 --- a/crates/eww/src/config/backend_window_options.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::config::xml_ext::XmlElement; -use anyhow::*; - -pub use backend::*; - -#[cfg(feature = "x11")] -mod backend { - - use super::*; - use crate::config::{EwwWindowType, StrutDefinition}; - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct BackendWindowOptions { - pub wm_ignore: bool, - pub sticky: bool, - pub window_type: EwwWindowType, - pub struts: StrutDefinition, - } - - impl BackendWindowOptions { - pub fn from_xml_element(xml: &XmlElement) -> Result { - let struts: Option = xml - .child("reserve") - .ok() - .map(StrutDefinition::from_xml_element) - .transpose() - .context("Failed to parse ")?; - - let window_type = xml.parse_optional_attr("windowtype")?; - - Ok(BackendWindowOptions { - wm_ignore: xml.parse_optional_attr("wm-ignore")?.unwrap_or(window_type.is_none() && struts.is_none()), - window_type: window_type.unwrap_or_default(), - sticky: xml.parse_optional_attr("sticky")?.unwrap_or(true), - struts: struts.unwrap_or_default(), - }) - } - } -} - -#[cfg(feature = "wayland")] -mod backend { - use super::*; - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct BackendWindowOptions { - pub exclusive: bool, - pub focusable: bool, - } - impl BackendWindowOptions { - pub fn from_xml_element(xml: &XmlElement) -> Result { - Ok(BackendWindowOptions { - exclusive: xml.parse_optional_attr("exclusive")?.unwrap_or(false), - focusable: xml.parse_optional_attr("focusable")?.unwrap_or(false), - }) - } - } -} - -#[cfg(feature = "no-x11-wayland")] -mod backend { - use super::*; - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct BackendWindowOptions; - impl BackendWindowOptions { - pub fn from_xml_element(xml: &XmlElement) -> Result { - Ok(BackendWindowOptions) - } - } -} diff --git a/crates/eww/src/config/element.rs b/crates/eww/src/config/element.rs deleted file mode 100644 index 98d9445..0000000 --- a/crates/eww/src/config/element.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::*; -use lazy_static::lazy_static; -use regex::Regex; -use std::ops::Range; - -use crate::{ - dynval::{AttrName, AttrVal}, - with_text_pos_context, -}; -use maplit::hashmap; -use std::collections::HashMap; - -#[derive(Debug, Clone, PartialEq)] -pub struct WidgetDefinition { - pub name: String, - pub structure: WidgetUse, - pub size: Option<(i32, i32)>, -} - -impl WidgetDefinition { - pub fn from_xml_element(xml: &XmlElement) -> Result { - with_text_pos_context! { xml => - if xml.tag_name() != "def" { - bail!( - "{} | Illegal element: only may be used in definition block, but found '{}'", - xml.text_pos(), - xml.as_tag_string() - ); - } - - WidgetDefinition { - name: xml.attr("name")?, - size: Option::zip(xml.parse_optional_attr("width")?, xml.parse_optional_attr("height")?), - structure: WidgetUse::from_xml_node(xml.only_child()?)?, - } - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct WidgetUse { - pub name: String, - pub children: Vec, - pub attrs: HashMap, - pub text_pos: Option, -} - -#[derive(Debug, Clone)] -pub struct PositionData { - pub range: Range, -} - -impl PartialEq for WidgetUse { - fn eq(&self, other: &WidgetUse) -> bool { - self.name == other.name && self.children == other.children && self.attrs == other.attrs - } -} - -impl WidgetUse { - pub fn new(name: String, children: Vec) -> Self { - WidgetUse { name, children, attrs: HashMap::new(), ..WidgetUse::default() } - } - - pub fn from_xml_node(xml: XmlNode) -> Result { - lazy_static! { - static ref PATTERN: Regex = Regex::new("\\{\\{(.*)\\}\\}").unwrap(); - }; - let text_pos = xml.text_pos(); - let widget_use = match xml { - XmlNode::Text(text) => WidgetUse::simple_text(AttrVal::parse_string(&text.text())), - XmlNode::Element(elem) => WidgetUse { - name: elem.tag_name().to_owned(), - children: with_text_pos_context! { elem => elem.children().map(WidgetUse::from_xml_node).collect::>()?}?, - attrs: elem - .attributes() - .iter() - .map(|attr| { - ( - AttrName(attr.name().to_owned()), - AttrVal::parse_string(&xml_ext::resolve_escaped_symbols(attr.value())), - ) - }) - .collect::>(), - ..WidgetUse::default() - }, - XmlNode::Ignored(_) => bail!("{} | Failed to parse node {:?} as widget use", xml.text_pos(), xml), - }; - Ok(widget_use.at_pos(text_pos)) - } - - pub fn simple_text(text: AttrVal) -> Self { - WidgetUse { - name: "label".to_owned(), - children: vec![], - attrs: hashmap! { AttrName("text".to_owned()) => text }, // TODO this hardcoded "text" is dumdum - ..WidgetUse::default() - } - } - - pub fn at_pos(mut self, text_pos: TextPos) -> Self { - self.text_pos = Some(text_pos); - self - } -} - -#[cfg(test)] -mod test { - use super::*; - use maplit::hashmap; - use pretty_assertions::assert_eq; - - #[test] - fn test_simple_text() { - let expected_attr_value = AttrVal::from_primitive("my text"); - let widget = WidgetUse::simple_text(expected_attr_value.clone()); - assert_eq!( - widget, - WidgetUse { - name: "label".to_owned(), - children: Vec::new(), - attrs: hashmap! { AttrName("text".to_owned()) => expected_attr_value}, - ..WidgetUse::default() - }, - ); - } - - #[test] - fn test_parse_widget_use() { - let input = r#" - - - foo - - "#; - let document = roxmltree::Document::parse(input).unwrap(); - let xml = XmlNode::from(document.root_element().clone()); - - let expected = WidgetUse { - name: "widget_name".to_owned(), - attrs: hashmap! { - AttrName("attr1".to_owned()) => AttrVal::from_primitive("hi"), - AttrName("attr2".to_owned()) => AttrVal::from_primitive("12"), - }, - children: vec![ - WidgetUse::new("child_widget".to_owned(), Vec::new()), - WidgetUse::simple_text(AttrVal::from_primitive("foo".to_owned())), - ], - ..WidgetUse::default() - }; - assert_eq!(expected, WidgetUse::from_xml_node(xml).unwrap()); - } - - #[test] - fn test_parse_widget_definition() { - let input = r#" - - test - - "#; - let document = roxmltree::Document::parse(input).unwrap(); - let xml = XmlNode::from(document.root_element().clone()); - - let expected = WidgetDefinition { - name: "foo".to_owned(), - size: Some((12, 20)), - structure: WidgetUse { - name: "layout".to_owned(), - children: vec![WidgetUse::simple_text(AttrVal::from_primitive("test"))], - attrs: HashMap::new(), - ..WidgetUse::default() - }, - }; - - assert_eq!(expected, WidgetDefinition::from_xml_element(xml.as_element().unwrap()).unwrap()); - } -} diff --git a/crates/eww/src/config/eww_config.rs b/crates/eww/src/config/eww_config.rs index a6644b8..ed1af4b 100644 --- a/crates/eww/src/config/eww_config.rs +++ b/crates/eww/src/config/eww_config.rs @@ -1,9 +1,7 @@ use anyhow::*; use std::collections::HashMap; use yuck::{ - config::{ - script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition, window_definition::WindowDefinition, - }, + config::{script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition}, parser::from_ast::FromAst, value::VarName, }; @@ -12,11 +10,13 @@ use simplexpr::dynval::DynVal; use std::path::PathBuf; +use super::{script_var, EwwWindowDefinition}; + /// Eww configuration structure. #[derive(Debug, Clone)] pub struct EwwConfig { widgets: HashMap, - windows: HashMap, + windows: HashMap, initial_variables: HashMap, script_vars: HashMap, pub filepath: PathBuf, @@ -37,7 +37,7 @@ impl EwwConfig { .map(|(name, window)| { Ok(( name, - WindowDefinition::generate(&config.widget_definitions, window) + EwwWindowDefinition::generate(&config.widget_definitions, window) .context("Failed expand window definition")?, )) }) @@ -51,21 +51,24 @@ impl EwwConfig { // TODO this is kinda ugly pub fn generate_initial_state(&self) -> Result> { - let mut vars = - self.script_vars.iter().map(|var| Ok((var.0.clone(), var.1.initial_value()?))).collect::>>()?; + let mut vars = self + .script_vars + .iter() + .map(|(name, var)| Ok((name.clone(), script_var::initial_value(var)?))) + .collect::>>()?; vars.extend(self.initial_variables.clone()); Ok(vars) } - pub fn get_windows(&self) -> &HashMap { + pub fn get_windows(&self) -> &HashMap { &self.windows } - pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> { + pub fn get_window(&self, name: &String) -> Result<&EwwWindowDefinition> { self.windows.get(name).with_context(|| format!("No window named '{}' exists", name)) } - pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVar> { + pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVarDefinition> { self.script_vars.get(name).with_context(|| format!("No script var named '{}' exists", name)) } diff --git a/crates/eww/src/config/inbuilt.rs b/crates/eww/src/config/inbuilt.rs index dd9efba..5f98212 100644 --- a/crates/eww/src/config/inbuilt.rs +++ b/crates/eww/src/config/inbuilt.rs @@ -1,14 +1,18 @@ -use crate::{ - config::{system_stats::*, PollScriptVar, ScriptVar, VarSource}, - dynval::{DynVal as PrimitiveValue, VarName}, -}; use std::{collections::HashMap, time::Duration}; +use simplexpr::dynval::DynVal; +use yuck::{ + config::script_var_definition::{PollScriptVar, ScriptVarDefinition, VarSource}, + value::VarName, +}; + +use crate::config::system_stats::*; + macro_rules! builtin_vars { ($interval:expr, $($name:literal => $fun:expr),*$(,)?) => {{ maplit::hashmap! { $( - VarName::from($name) => ScriptVar::Poll(PollScriptVar { + VarName::from($name) => ScriptVarDefinition::Poll(PollScriptVar { name: VarName::from($name), command: VarSource::Function($fun), interval: $interval, @@ -17,19 +21,19 @@ macro_rules! builtin_vars { } }}} -pub fn get_inbuilt_vars() -> HashMap { +pub fn get_inbuilt_vars() -> HashMap { builtin_vars! {Duration::new(2, 0), // @desc EWW_TEMPS - Heat of the components in Celcius\nExample: `{{(CPU_TEMPS.core_1 + CPU_TEMPS.core_2) / 2}}` - "EWW_TEMPS" => || Ok(Primitivedynval::from(cores())), + "EWW_TEMPS" => || Ok(DynVal::from(cores())), // @desc EWW_RAM - The current RAM + Swap usage - "EWW_RAM" => || Ok(Primitivedynval::from(format!("{:.2}", ram()))), + "EWW_RAM" => || Ok(DynVal::from(format!("{:.2}", ram()))), // @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs)\nExample: `{{EWW_DISK["/"]}}` - "EWW_DISK" => || Ok(Primitivedynval::from(disk())), + "EWW_DISK" => || Ok(DynVal::from(disk())), // @desc EWW_BATTERY - Battery capacity in procent of the main battery - "EWW_BATTERY" => || Ok(Primitivedynval::from( + "EWW_BATTERY" => || Ok(DynVal::from( match get_battery_capacity() { Err(e) => { log::error!("Couldn't get the battery capacity: {:?}", e); @@ -40,9 +44,9 @@ pub fn get_inbuilt_vars() -> HashMap { )), // @desc EWW_CPU_USAGE - Average CPU usage (all cores) since the last update (No MacOS support) - "EWW_CPU_USAGE" => || Ok(Primitivedynval::from(get_avg_cpu_usage())), + "EWW_CPU_USAGE" => || Ok(DynVal::from(get_avg_cpu_usage())), // @desc EWW_NET - Bytes up/down on all interfaces - "EWW_NET" => || Ok(Primitivedynval::from(net())), + "EWW_NET" => || Ok(DynVal::from(net())), } } diff --git a/crates/eww/src/config/mod.rs b/crates/eww/src/config/mod.rs index 66fe9bd..19ed762 100644 --- a/crates/eww/src/config/mod.rs +++ b/crates/eww/src/config/mod.rs @@ -1,33 +1,8 @@ -use crate::{ - util, - dynval::{DynVal, VarName}, -}; - -use anyhow::*; - -use element::*; -use xml_ext::*; - -pub mod backend_window_options; -pub mod element; pub mod eww_config; pub mod inbuilt; pub mod script_var; pub mod system_stats; pub mod window_definition; -pub mod window_geometry; -pub mod xml_ext; pub use eww_config::*; pub use script_var::*; pub use window_definition::*; -pub use window_geometry::*; - -#[macro_export] -macro_rules! ensure_xml_tag_is { - ($element:ident, $name:literal) => { - ensure!( - $element.tag_name() == $name, - anyhow!("{} | Tag needed to be of type '{}', but was: {}", $element.text_pos(), $name, $element.as_tag_string()) - ) - }; -} diff --git a/crates/eww/src/config/script_var.rs b/crates/eww/src/config/script_var.rs index 0f965e6..39bddfe 100644 --- a/crates/eww/src/config/script_var.rs +++ b/crates/eww/src/config/script_var.rs @@ -1,80 +1,22 @@ use std::process::Command; use anyhow::*; +use simplexpr::dynval::DynVal; +use yuck::config::script_var_definition::{ScriptVarDefinition, VarSource}; -use crate::ensure_xml_tag_is; - -use super::*; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum VarSource { - Shell(String), - Function(fn() -> Result), -} -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PollScriptVar { - pub name: VarName, - pub command: VarSource, - pub interval: std::time::Duration, -} - -impl PollScriptVar { - pub fn run_once(&self) -> Result { - match &self.command { - VarSource::Shell(x) => run_command(x), - VarSource::Function(x) => x(), - } +pub fn initial_value(var: &ScriptVarDefinition) -> Result { + match var { + ScriptVarDefinition::Poll(x) => match &x.command { + VarSource::Function(f) => { + f().map_err(|err| anyhow!(err)).with_context(|| format!("Failed to compute initial value for {}", &var.name())) + } + VarSource::Shell(f) => run_command(f).with_context(|| format!("Failed to compute initial value for {}", &var.name())), + }, + ScriptVarDefinition::Tail(_) => Ok(DynVal::from_string(String::new())), } } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TailScriptVar { - pub name: VarName, - pub command: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ScriptVar { - Poll(PollScriptVar), - Tail(TailScriptVar), -} - -impl ScriptVar { - pub fn name(&self) -> &VarName { - match self { - ScriptVar::Poll(x) => &x.name, - ScriptVar::Tail(x) => &x.name, - } - } - - pub fn initial_value(&self) -> Result { - match self { - ScriptVar::Poll(x) => match &x.command { - VarSource::Function(f) => f().with_context(|| format!("Failed to compute initial value for {}", &self.name())), - VarSource::Shell(f) => { - run_command(f).with_context(|| format!("Failed to compute initial value for {}", &self.name())) - } - }, - ScriptVar::Tail(_) => Ok(DynVal::from_string(String::new())), - } - } - - pub fn from_xml_element(xml: XmlElement) -> Result { - ensure_xml_tag_is!(xml, "script-var"); - - let name = VarName(xml.attr("name")?); - let command = xml.only_child()?.as_text()?.text(); - if let Ok(interval) = xml.attr("interval") { - let interval = util::parse_duration(&interval)?; - Ok(ScriptVar::Poll(PollScriptVar { name, command: crate::config::VarSource::Shell(command), interval })) - } else { - Ok(ScriptVar::Tail(TailScriptVar { name, command })) - } - } -} - /// Run a command and get the output -fn run_command(cmd: &str) -> Result { +pub fn run_command(cmd: &str) -> Result { log::debug!("Running command: {}", cmd); let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?; let output = output.trim_matches('\n'); diff --git a/crates/eww/src/config/window_definition.rs b/crates/eww/src/config/window_definition.rs index a8ac3b2..750e8fb 100644 --- a/crates/eww/src/config/window_definition.rs +++ b/crates/eww/src/config/window_definition.rs @@ -1,41 +1,14 @@ -use super::*; -use crate::{dynval::NumWithUnit, ensure_xml_tag_is, widgets::widget_node}; -use derive_more::*; -use serde::{Deserialize, Serialize}; -use smart_default::SmartDefault; -use std::{collections::HashMap, str::FromStr}; +use std::collections::HashMap; + +use anyhow::*; use yuck::config::{ - backend_window_options::StrutDefinition, + backend_window_options::BackendWindowOptions, + widget_definition::WidgetDefinition, window_definition::{WindowDefinition, WindowStacking}, window_geometry::WindowGeometry, }; -#[derive(Debug, Clone, PartialEq)] -pub enum EwwWindowType { - Dock, - Dialog, - Toolbar, - Normal, -} -impl FromStr for EwwWindowType { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s { - "dock" => Ok(Self::Dock), - "toolbar" => Ok(Self::Toolbar), - "dialog" => Ok(Self::Dialog), - "normal" => Ok(Self::Normal), - x => Err(anyhow!("Unknown windowtype provided '{}'. Possible values are: dock, toolbar, dialog, normal", x)), - } - } -} - -impl Default for EwwWindowType { - fn default() -> Self { - Self::Normal - } -} +use crate::widgets::widget_node; /// Full window-definition containing the fully expanded widget tree. /// **Use this** rather than `[RawEwwWindowDefinition]`. @@ -43,20 +16,12 @@ impl Default for EwwWindowType { pub struct EwwWindowDefinition { pub name: String, - pub geometry: WindowGeometry, + pub geometry: Option, pub stacking: WindowStacking, pub monitor_number: Option, pub widget: Box, - pub focusable: bool, - - #[cfg(feature = "x11")] - pub window_type: EwwWindowType, - - #[cfg(feature = "x11")] - pub struts: StrutDefinition, - - #[cfg(feature = "wayland")] - pub exclusive: bool, + pub resizable: bool, + pub backend_options: BackendWindowOptions, } impl EwwWindowDefinition { @@ -65,15 +30,10 @@ impl EwwWindowDefinition { name: window.name, geometry: window.geometry, stacking: window.stacking, - monitor_number: window.screen_number, + monitor_number: window.monitor_number, + resizable: window.resizable, widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?, - focusable: window.focusable, - #[cfg(feature = "x11")] - window_type: window.window_type, - #[cfg(feature = "x11")] - struts: window.struts, - #[cfg(feature = "wayland")] - exclusive: window.exclusive, + backend_options: window.backend_options, }) } } diff --git a/crates/eww/src/config/window_geometry.rs b/crates/eww/src/config/window_geometry.rs deleted file mode 100644 index 6e11bfe..0000000 --- a/crates/eww/src/config/window_geometry.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::dynval::Coords; -use anyhow::*; -use serde::{Deserialize, Serialize}; -use smart_default::SmartDefault; - -use std::fmt; - -use super::xml_ext::XmlElement; - -#[derive(Debug, derive_more::Display, Clone, Copy, Eq, PartialEq, SmartDefault, Serialize, Deserialize)] -pub enum AnchorAlignment { - #[display(fmt = "start")] - #[default] - START, - #[display(fmt = "center")] - CENTER, - #[display(fmt = "end")] - END, -} - -impl AnchorAlignment { - pub fn from_x_alignment(s: &str) -> Result { - match s { - "l" | "left" => Ok(AnchorAlignment::START), - "c" | "center" => Ok(AnchorAlignment::CENTER), - "r" | "right" => Ok(AnchorAlignment::END), - _ => bail!(r#"couldn't parse '{}' as x-alignment. Must be one of "left", "center", "right""#, s), - } - } - - pub fn from_y_alignment(s: &str) -> Result { - match s { - "t" | "top" => Ok(AnchorAlignment::START), - "c" | "center" => Ok(AnchorAlignment::CENTER), - "b" | "bottom" => Ok(AnchorAlignment::END), - _ => bail!(r#"couldn't parse '{}' as y-alignment. Must be one of "top", "center", "bottom""#, s), - } - } - - pub fn alignment_to_coordinate(&self, size_inner: i32, size_container: i32) -> i32 { - match self { - AnchorAlignment::START => 0, - AnchorAlignment::CENTER => (size_container / 2) - (size_inner / 2), - AnchorAlignment::END => size_container - size_inner, - } - } -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Serialize, Deserialize)] -pub struct AnchorPoint { - pub x: AnchorAlignment, - pub y: AnchorAlignment, -} - -impl std::fmt::Display for AnchorPoint { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use AnchorAlignment::*; - match (self.x, self.y) { - (CENTER, CENTER) => write!(f, "center"), - (x, y) => write!( - f, - "{} {}", - match x { - START => "left", - CENTER => "center", - END => "right", - }, - match y { - START => "top", - CENTER => "center", - END => "bottom", - } - ), - } - } -} - -impl std::str::FromStr for AnchorPoint { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - if s == "center" { - Ok(AnchorPoint { x: AnchorAlignment::CENTER, y: AnchorAlignment::CENTER }) - } else { - let (first, second) = s - .split_once(' ') - .context("Failed to parse anchor: Must either be \"center\" or be formatted like \"top left\"")?; - let x_y_result: Result<_> = try { - AnchorPoint { x: AnchorAlignment::from_x_alignment(first)?, y: AnchorAlignment::from_y_alignment(second)? } - }; - x_y_result.or_else(|_| { - Ok(AnchorPoint { x: AnchorAlignment::from_x_alignment(second)?, y: AnchorAlignment::from_y_alignment(first)? }) - }) - } - } -} - -#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] -pub struct EwwWindowGeometry { - pub anchor_point: AnchorPoint, - pub offset: Coords, - pub size: Coords, -} - -impl EwwWindowGeometry { - pub fn from_xml_element(xml: XmlElement) -> Result { - Ok(EwwWindowGeometry { - anchor_point: xml.parse_optional_attr("anchor")?.unwrap_or_default(), - size: Coords { - x: xml.parse_optional_attr("width")?.unwrap_or_default(), - y: xml.parse_optional_attr("height")?.unwrap_or_default(), - }, - offset: Coords { - x: xml.parse_optional_attr("x")?.unwrap_or_default(), - y: xml.parse_optional_attr("y")?.unwrap_or_default(), - }, - }) - } - - pub fn override_if_given(&self, anchor_point: Option, offset: Option, size: Option) -> Self { - EwwWindowGeometry { - anchor_point: anchor_point.unwrap_or(self.anchor_point), - offset: offset.unwrap_or(self.offset), - size: size.unwrap_or(self.size), - } - } -} - -impl std::fmt::Display for EwwWindowGeometry { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}-{} ({})", self.offset, self.size, self.anchor_point) - } -} - -impl EwwWindowGeometry { - /// Calculate the window rectangle given the configured window geometry - pub fn get_window_rectangle(&self, screen_rect: gdk::Rectangle) -> gdk::Rectangle { - let (offset_x, offset_y) = self.offset.relative_to(screen_rect.width, screen_rect.height); - let (width, height) = self.size.relative_to(screen_rect.width, screen_rect.height); - let x = screen_rect.x + offset_x + self.anchor_point.x.alignment_to_coordinate(width, screen_rect.width); - let y = screen_rect.y + offset_y + self.anchor_point.y.alignment_to_coordinate(height, screen_rect.height); - gdk::Rectangle { x, y, width, height } - } -} diff --git a/crates/eww/src/config/xml_ext.rs b/crates/eww/src/config/xml_ext.rs deleted file mode 100644 index b8cdf9c..0000000 --- a/crates/eww/src/config/xml_ext.rs +++ /dev/null @@ -1,307 +0,0 @@ -use crate::util::StringExt; -use anyhow::*; -use itertools::Itertools; -use std::fmt; - -#[macro_export] -macro_rules! with_text_pos_context { - ($node:expr => $($code:tt)*) => {{ - let result: Result<_> = try { $($code)* }; - result.with_context(|| anyhow!("at: {}", $node.text_pos())) - }}; -} - -/// resolve symbols such as " to replace them with the actual " symbol -pub fn resolve_escaped_symbols(s: &str) -> String { - s.replace(""", "\"").replace("<", "<").replace(">", ">") -} - -#[derive(Debug, Clone)] -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(PartialEq, Eq, Clone, Copy, derive_more::Display)] -#[display(fmt = "{}:{}", row, col)] -pub struct TextPos { - pub row: u32, - pub col: u32, -} - -impl std::fmt::Debug for TextPos { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self) - } -} - -impl From for TextPos { - fn from(x: roxmltree::TextPos) -> Self { - TextPos { row: x.row, col: x.col } - } -} - -/// Get the part of a string that is selected by the start and end TextPos. -/// Will panic if the range is out of bounds in any way. -fn get_text_from_text_range(s: &str, (start_pos, end_pos): (TextPos, TextPos)) -> String { - let mut code_text = - s.lines().dropping(start_pos.row as usize - 1).take(end_pos.row as usize - (start_pos.row as usize - 1)).collect_vec(); - if let Some(first_line) = code_text.first_mut() { - *first_line = first_line.split_at(start_pos.col as usize - 1).1; - } - if let Some(last_line) = code_text.last_mut() { - *last_line = last_line.split_at(end_pos.col as usize - 1).0; - } - resolve_escaped_symbols(&code_text.join("\n")) -} - -impl<'a, 'b> XmlNode<'a, 'b> { - pub fn get_sourcecode(&self) -> String { - let input_text = self.node().document().input_text(); - let range = self.node().range(); - let start_pos = self.node().document().text_pos_at(range.start).into(); - let end_pos = self.node().document().text_pos_at(range.end).into(); - get_text_from_text_range(input_text, (start_pos, end_pos)) - } - - pub fn as_text_or_sourcecode(&self) -> String { - self.as_text().map(|c| resolve_escaped_symbols(&c.text())).unwrap_or_else(|_| self.get_sourcecode()) - } - - 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)), - } - } - - pub fn text_range(&self) -> std::ops::Range { - self.node().range() - } - - pub fn text_pos(&self) -> TextPos { - let document = self.node().document(); - let range = self.node().range(); - document.text_pos_at(range.start).into() - } - - fn node(&self) -> roxmltree::Node<'a, 'b> { - match self { - XmlNode::Text(x) => x.0, - XmlNode::Element(x) => x.0, - XmlNode::Ignored(x) => *x, - } - } -} - -#[derive(Debug, Clone)] -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.text()) - } -} - -impl<'a, 'b> XmlText<'a, 'b> { - pub fn text(&self) -> String { - self.0.text().map(resolve_escaped_symbols).unwrap_or_default().trim_lines().trim_matches('\n').to_owned() - } - - pub fn text_pos(&self) -> TextPos { - let document = self.0.document(); - let range = self.0.range(); - document.text_pos_at(range.start).into() - } -} - -#[derive(Debug, Clone)] -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"); - - if children.is_empty() { - write!(f, "{}", self.as_tag_string(), self.tag_name()) - } else { - write!(f, "{}\n{}\n", self.as_tag_string(), children, self.tag_name()) - } - } -} - -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 { - with_text_pos_context! { self => - 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 { - 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 { - 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 { - with_text_pos_context! { self => - self.0 - .attribute(key) - .map(resolve_escaped_symbols) - .with_context(|| anyhow!("'{}' missing attribute '{}'", self.as_tag_string(), key))? - } - } - - pub fn optional_attr Result>(&self, key: &str, parse: F) -> Result> { - match self.0.attribute(key) { - Some(value) => parse(value) - .with_context(|| format!("Parsing the value of {}=\"{}\" in <{}>", key, value, self.tag_name())) - .map(Some), - None => Ok(None), - } - } - - pub fn parse_optional_attr, O: std::str::FromStr>(&self, key: &str) -> Result> { - match self.0.attribute(key) { - Some(value) => value - .parse::() - .map_err(|e| anyhow!(e)) - .with_context(|| format!("Parsing the value of {}=\"{}\" in <{}>", key, value, self.tag_name())) - .map(Some), - None => Ok(None), - } - } - - pub fn only_child(&self) -> Result { - with_text_pos_context! { self => - let mut children_iter = self.children(); - let only_child = children_iter - .next() - .with_context(|| anyhow!("'{}' had no children", self.as_tag_string()))?; - if children_iter.next().is_some() { - bail!("'{}' had more than one child", &self); - } - only_child - } - } - - pub fn only_child_element(&self) -> Result { - with_text_pos_context! { self => - self.only_child()?.as_element()?.clone() - } - } - - pub fn text_pos(&self) -> TextPos { - let document = self.0.document(); - let range = self.0.range(); - document.text_pos_at(range.start).into() - } -} - -impl<'a, 'b> From> for XmlNode<'a, 'b> { - fn from(elem: XmlElement<'a, 'b>) -> Self { - XmlNode::Element(elem) - } -} - -impl<'a, 'b> From> for XmlNode<'a, 'b> { - fn from(elem: XmlText<'a, 'b>) -> Self { - XmlNode::Text(elem) - } -} - -impl<'a, 'b> From> 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) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - #[test] - pub fn test_parse_sourcecode_singleline() { - let input = "whatever"; - let document = roxmltree::Document::parse(&input).unwrap(); - let root_node = XmlNode::from(document.root_element()); - assert_eq!(root_node.as_element().unwrap().only_child().unwrap().as_text_or_sourcecode(), "whatever".to_string()); - } - - #[test] - pub fn test_parse_sourcecode_multiline() { - let input = r#" -this is -multiline - "#; - let document = roxmltree::Document::parse(&input).unwrap(); - let root_node = XmlNode::from(document.root_element()); - assert_eq!( - root_node.as_element().unwrap().only_child().unwrap().as_text_or_sourcecode(), - "this is\nmultiline".to_string() - ); - } - - #[test] - pub fn test_parse_sourcecode_code() { - let input = r#" -if [ "this" == '$that' ]; then - echo `hi` -fi - "#; - let document = roxmltree::Document::parse(&input).unwrap(); - let root_node = XmlNode::from(document.root_element()); - assert_eq!( - root_node.as_element().unwrap().only_child().unwrap().as_text_or_sourcecode(), - "if [ \"this\" == '$that' ]; then\necho `hi`\nfi".to_string() - ); - } -} diff --git a/crates/eww/src/display_backend.rs b/crates/eww/src/display_backend.rs index 8734842..f2c176d 100644 --- a/crates/eww/src/display_backend.rs +++ b/crates/eww/src/display_backend.rs @@ -91,7 +91,6 @@ mod platform { #[cfg(feature = "x11")] mod platform { - use crate::config::{EwwWindowDefinition, EwwWindowType, Side, WindowStacking}; use anyhow::*; use gdkx11; use gtk::{self, prelude::*}; @@ -103,6 +102,12 @@ mod platform { protocol::xproto::*, rust_connection::{DefaultStream, RustConnection}, }; + use yuck::config::{ + backend_window_options::{Side, WindowType}, + window_definition::WindowStacking, + }; + + use crate::config::EwwWindowDefinition; pub fn initialize_window(window_def: &EwwWindowDefinition, _monitor: gdk::Rectangle) -> Option { let window_type = if window_def.backend_options.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel }; @@ -208,11 +213,11 @@ mod platform { self.atoms._NET_WM_WINDOW_TYPE, self.atoms.ATOM, &[match window_def.backend_options.window_type { - EwwWindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK, - EwwWindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL, - EwwWindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG, - EwwWindowType::Toolbar => self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, - EwwWindowType::Utility => self.atoms._NET_WM_WINDOW_TYPE_UTILITY, + WindowType::Dock => self.atoms._NET_WM_WINDOW_TYPE_DOCK, + WindowType::Normal => self.atoms._NET_WM_WINDOW_TYPE_NORMAL, + WindowType::Dialog => self.atoms._NET_WM_WINDOW_TYPE_DIALOG, + WindowType::Toolbar => self.atoms._NET_WM_WINDOW_TYPE_TOOLBAR, + WindowType::Utility => self.atoms._NET_WM_WINDOW_TYPE_UTILITY, }], )? .check()?; diff --git a/crates/eww/src/eww_state.rs b/crates/eww/src/eww_state.rs index 4160c1e..20bb448 100644 --- a/crates/eww/src/eww_state.rs +++ b/crates/eww/src/eww_state.rs @@ -1,22 +1,20 @@ -use crate::{ - config::window_definition::WindowName, - dynval::{AttrName, AttrValElement, VarName}, -}; use anyhow::*; use std::{collections::HashMap, sync::Arc}; +use yuck::value::{AttrName, VarName}; -use crate::dynval::{AttrVal, DynVal}; +use simplexpr::{dynval::DynVal, SimplExpr}; /// 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. pub struct StateChangeHandler { func: Box) -> Result<()> + 'static>, - unresolved_values: HashMap, + unresolved_values: HashMap, } impl StateChangeHandler { - fn used_variables(&self) -> impl Iterator { - self.unresolved_values.iter().flat_map(|(_, value)| value.var_refs()) + fn used_variables(&self) -> impl Iterator + '_ { + // TODO fix this clone + self.unresolved_values.iter().flat_map(|(_, value)| value.var_refs()).map(|x| VarName(x.to_string())) } /// Run the StateChangeHandler. @@ -60,7 +58,7 @@ impl EwwWindowState { /// window-specific state-change handlers. #[derive(Default)] pub struct EwwState { - windows: HashMap, + windows: HashMap, variables_state: HashMap, } @@ -80,7 +78,7 @@ impl EwwState { } /// remove all state stored specific to one window - pub fn clear_window_state(&mut self, window_name: &WindowName) { + pub fn clear_window_state(&mut self, window_name: &str) { self.windows.remove(window_name); } @@ -108,22 +106,17 @@ impl EwwState { } /// resolves a value if possible, using the current eww_state. - pub fn resolve_once<'a>(&'a self, value: &'a AttrVal) -> Result { - value - .iter() - .map(|element| match element { - AttrValElement::Primitive(primitive) => Ok(primitive.clone()), - AttrValElement::Expr(expr) => expr.clone().eval(&self.variables_state), - }) - .collect() + pub fn resolve_once<'a>(&'a self, value: &'a SimplExpr) -> Result { + // TODO fix this clone + Ok(value.clone().eval(&self.variables_state.into_iter().map(|(k, v)| (k.0, v)).collect())?) } /// Resolve takes a function that applies a set of fully resolved attribute /// values to it's gtk widget. pub fn resolve) -> Result<()> + 'static + Clone>( &mut self, - window_name: &WindowName, - required_attributes: HashMap, + window_name: &str, + required_attributes: HashMap, set_value: F, ) { let handler = StateChangeHandler { func: Box::new(set_value), unresolved_values: required_attributes }; @@ -132,7 +125,7 @@ impl EwwState { // only store the handler if at least one variable is being used if handler.used_variables().next().is_some() { - self.windows.entry(window_name.clone()).or_insert_with(EwwWindowState::default).put_handler(handler); + self.windows.entry(window_name.to_string()).or_insert_with(EwwWindowState::default).put_handler(handler); } } @@ -140,7 +133,7 @@ impl EwwState { self.windows.values().flat_map(|w| w.state_change_handlers.keys()) } - pub fn vars_referenced_in(&self, window_name: &WindowName) -> std::collections::HashSet<&VarName> { + pub fn vars_referenced_in(&self, window_name: &str) -> std::collections::HashSet<&VarName> { self.windows.get(window_name).map(|window| window.state_change_handlers.keys().collect()).unwrap_or_default() } } diff --git a/crates/eww/src/opts.rs b/crates/eww/src/opts.rs index d6ef99c..95872d5 100644 --- a/crates/eww/src/opts.rs +++ b/crates/eww/src/opts.rs @@ -1,13 +1,14 @@ use anyhow::*; use serde::{Deserialize, Serialize}; +use simplexpr::dynval::DynVal; use structopt::StructOpt; - -use crate::{ - app, - config::{AnchorPoint, WindowName}, - dynval::{Coords, DynVal, VarName}, +use yuck::{ + config::window_geometry::AnchorPoint, + value::{Coords, VarName}, }; +use crate::app; + /// Struct that gets generated from `RawOpt`. #[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct Opt { @@ -68,7 +69,7 @@ pub enum ActionWithServer { #[structopt(name = "open", alias = "o")] OpenWindow { /// Name of the window you want to open. - window_name: WindowName, + window_name: String, /// Monitor-index the window should open on #[structopt(short, long)] @@ -90,11 +91,11 @@ pub enum ActionWithServer { /// Open multiple windows at once. /// NOTE: This will in the future be part of eww open, and will then be removed. #[structopt(name = "open-many")] - OpenMany { windows: Vec }, + OpenMany { windows: Vec }, /// Close the window with the given name #[structopt(name = "close", alias = "c")] - CloseWindow { window_name: WindowName }, + CloseWindow { window_name: String }, /// Reload the configuration #[structopt(name = "reload", alias = "r")] diff --git a/crates/eww/src/script_var_handler.rs b/crates/eww/src/script_var_handler.rs index 372a77b..ced0b17 100644 --- a/crates/eww/src/script_var_handler.rs +++ b/crates/eww/src/script_var_handler.rs @@ -1,17 +1,16 @@ use std::collections::HashMap; -use crate::{ - app, config, - dynval::{DynVal, VarName}, -}; +use crate::app; use anyhow::*; use app::DaemonCommand; +use simplexpr::dynval::DynVal; use tokio::{ io::{AsyncBufReadExt, BufReader}, sync::mpsc::UnboundedSender, }; use tokio_util::sync::CancellationToken; +use yuck::{config::script_var_definition::{PollScriptVar, ScriptVarDefinition, TailScriptVar}, value::VarName}; /// Initialize the script var handler, and return a handle to that handler, which can be used to control /// the script var execution. @@ -53,7 +52,7 @@ pub struct ScriptVarHandlerHandle { impl ScriptVarHandlerHandle { /// Add a new script-var that should be executed. - pub fn add(&self, script_var: config::ScriptVar) { + pub fn add(&self, script_var: ScriptVarDefinition) { crate::print_result_err!( "while forwarding instruction to script-var handler", self.msg_send.send(ScriptVarHandlerMsg::AddVar(script_var)) @@ -80,22 +79,22 @@ impl ScriptVarHandlerHandle { /// Message enum used by the ScriptVarHandlerHandle to communicate to the ScriptVarHandler #[derive(Debug, Eq, PartialEq)] enum ScriptVarHandlerMsg { - AddVar(config::ScriptVar), + AddVar(ScriptVarDefinition), Stop(VarName), StopAll, } -/// Handler that manages running and updating [ScriptVar]s +/// Handler that manages running and updating [ScriptVarDefinition]s struct ScriptVarHandler { tail_handler: TailVarHandler, poll_handler: PollVarHandler, } impl ScriptVarHandler { - async fn add(&mut self, script_var: config::ScriptVar) { + async fn add(&mut self, script_var: ScriptVarDefinition) { match script_var { - config::ScriptVar::Poll(var) => self.poll_handler.start(var).await, - config::ScriptVar::Tail(var) => self.tail_handler.start(var).await, + ScriptVarDefinition::Poll(var) => self.poll_handler.start(var).await, + ScriptVarDefinition::Tail(var) => self.tail_handler.start(var).await, }; } @@ -126,14 +125,14 @@ impl PollVarHandler { Ok(handler) } - async fn start(&mut self, var: config::PollScriptVar) { + async fn start(&mut self, var: PollScriptVar) { log::debug!("starting poll var {}", &var.name); let cancellation_token = CancellationToken::new(); self.poll_handles.insert(var.name.clone(), cancellation_token.clone()); let evt_send = self.evt_send.clone(); tokio::spawn(async move { let result: Result<_> = try { - evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?; + evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?; }; crate::print_result_err!("while running script-var command", &result); @@ -141,7 +140,7 @@ impl PollVarHandler { _ = cancellation_token.cancelled() => break, _ = tokio::time::sleep(var.interval) => { let result: Result<_> = try { - evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), var.run_once()?)]))?; + evt_send.send(app::DaemonCommand::UpdateVars(vec![(var.name.clone(), run_poll_once(&var)?)]))?; }; crate::print_result_err!("while running script-var command", &result); } @@ -161,6 +160,13 @@ impl PollVarHandler { } } +fn run_poll_once(var: &PollScriptVar) -> Result { + match &var.command { + yuck::config::script_var_definition::VarSource::Shell(x) => crate::config::script_var::run_command(x), + yuck::config::script_var_definition::VarSource::Function(x) => x().map_err(|e| anyhow!(e)), + } +} + impl Drop for PollVarHandler { fn drop(&mut self) { self.stop_all(); @@ -178,7 +184,7 @@ impl TailVarHandler { Ok(handler) } - async fn start(&mut self, var: config::TailScriptVar) { + async fn start(&mut self, var: TailScriptVar) { log::debug!("starting poll var {}", &var.name); let cancellation_token = CancellationToken::new(); self.tail_process_handles.insert(var.name.clone(), cancellation_token.clone()); diff --git a/crates/eww/src/widgets/mod.rs b/crates/eww/src/widgets/mod.rs index d772fdc..f36d48b 100644 --- a/crates/eww/src/widgets/mod.rs +++ b/crates/eww/src/widgets/mod.rs @@ -1,12 +1,9 @@ -use crate::{ - config::{element::WidgetDefinition, window_definition::WindowName}, - eww_state::*, -}; +use crate::eww_state::*; use anyhow::*; use gtk::prelude::*; use itertools::Itertools; use std::collections::HashMap; -use yuck::value::AttrName; +use yuck::{config::widget_definition::WidgetDefinition, value::AttrName}; use std::process::Command; use widget_definitions::*; @@ -45,7 +42,7 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> { eww_state: &'a mut EwwState, widget: &'b widget_node::Generic, unhandled_attrs: Vec<&'c AttrName>, - window_name: &'d WindowName, + window_name: &'d str, widget_definitions: &'e HashMap, } @@ -60,7 +57,7 @@ struct BuilderArgs<'a, 'b, 'c, 'd, 'e> { /// widget name. fn build_builtin_gtk_widget( eww_state: &mut EwwState, - window_name: &WindowName, + window_name: &str, widget_definitions: &HashMap, widget: &widget_node::Generic, ) -> Result> { @@ -72,7 +69,7 @@ fn build_builtin_gtk_widget( return result.with_context(|| { format!( "{}Error building widget {}", - bargs.widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(), + bargs.widget.span.map(|x| format!("{} |", x)).unwrap_or_default(), bargs.widget.name, ) }) @@ -87,7 +84,7 @@ fn build_builtin_gtk_widget( let child_widget = child.render(bargs.eww_state, window_name, widget_definitions).with_context(|| { format!( "{}error while building child '{:#?}' of '{}'", - widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(), + widget.span.map(|x| format!("{} |", x)).unwrap_or_default(), &child, >k_widget.get_widget_name() ) @@ -108,7 +105,7 @@ fn build_builtin_gtk_widget( if !bargs.unhandled_attrs.is_empty() { log::error!( "{}: Unknown attribute used in {}: {}", - widget.text_pos.map(|x| format!("{} | ", x)).unwrap_or_default(), + widget.span.map(|x| format!("{} | ", x)).unwrap_or_default(), widget.name, bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ") ) diff --git a/crates/eww/src/widgets/widget_definitions.rs b/crates/eww/src/widgets/widget_definitions.rs index 2f69689..2494042 100644 --- a/crates/eww/src/widgets/widget_definitions.rs +++ b/crates/eww/src/widgets/widget_definitions.rs @@ -45,7 +45,9 @@ pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result &str; - fn get_text_pos(&self) -> Option; + fn get_span(&self) -> Option; /// Generate a [gtk::Widget] from a [element::WidgetUse]. /// @@ -24,7 +23,7 @@ pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync { fn render( &self, eww_state: &mut EwwState, - window_name: &WindowName, + window_name: &str, widget_definitions: &HashMap, ) -> Result; } @@ -34,7 +33,7 @@ dyn_clone::clone_trait_object!(WidgetNode); #[derive(Debug, Clone)] pub struct UserDefined { name: String, - text_pos: Option, + span: Option, content: Box, } @@ -43,14 +42,14 @@ impl WidgetNode for UserDefined { &self.name } - fn get_text_pos(&self) -> Option { - self.text_pos + fn get_span(&self) -> Option { + self.span } fn render( &self, eww_state: &mut EwwState, - window_name: &WindowName, + window_name: &str, widget_definitions: &HashMap, ) -> Result { self.content.render(eww_state, window_name, widget_definitions) @@ -60,19 +59,20 @@ impl WidgetNode for UserDefined { #[derive(Debug, Clone)] pub struct Generic { pub name: String, - pub text_pos: Option, + pub span: Option, pub children: Vec>, - pub attrs: HashMap, + pub attrs: HashMap, } impl Generic { - pub fn get_attr(&self, key: &str) -> Result<&AttrVal> { + pub fn get_attr(&self, key: &str) -> Result<&SimplExpr> { 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 { - self.attrs.iter().flat_map(|(_, value)| value.var_refs()) + pub fn referenced_vars(&self) -> impl Iterator + '_ { + // TODO fix this clone + self.attrs.iter().flat_map(|(_, value)| value.var_refs()).map(|x| VarName(x.to_string())) } } @@ -81,14 +81,14 @@ impl WidgetNode for Generic { &self.name } - fn get_text_pos(&self) -> Option { - self.text_pos + fn get_span(&self) -> Option { + self.span } fn render( &self, eww_state: &mut EwwState, - window_name: &WindowName, + window_name: &str, widget_definitions: &HashMap, ) -> Result { crate::widgets::build_builtin_gtk_widget(eww_state, window_name, widget_definitions, self)? @@ -98,25 +98,42 @@ impl WidgetNode for Generic { pub fn generate_generic_widget_node( defs: &HashMap, - local_env: &HashMap, + local_env: &HashMap, w: WidgetUse, ) -> Result> { 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 .attrs .into_iter() - .map(|(name, value)| (VarName(name.0), value.resolve_one_level(local_env))) - .collect::>(); + .map(|(name, value)| { + Ok(( + VarName(name.0), + SimplExpr::Literal(value.value.span().into(), value.value.as_simplexpr()?.resolve_one_level(local_env)?), + )) + }) + .collect::>>()?; - 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 })) + let content = generate_generic_widget_node(defs, &new_local_env, def.widget.clone())?; + Ok(Box::new(UserDefined { name: w.name, span: Some(w.span), 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(), + span: Some(w.span), + attrs: w + .attrs + .attrs + .into_iter() + .map(|(name, value)| { + Ok(( + VarName(name.0), + SimplExpr::Literal(value.value.span().into(), value.value.as_simplexpr()?.resolve_one_level(local_env)?), + )) + }) + .collect()?, + children: w .children .into_iter() @@ -126,42 +143,41 @@ pub fn generate_generic_widget_node( } } -#[cfg(test)] -mod test { - use super::*; - use crate::config::xml_ext::*; - use maplit::hashmap; - #[test] - fn test_generic_generate() { - let w_def1 = { - let input = r#"{{nested1}}{{raw1}}"#; - 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#""#; - 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#""#; - let document = roxmltree::Document::parse(input).unwrap(); - let xml = XmlNode::from(document.root_element().clone()); - WidgetUse::from_xml_node(xml).unwrap() - }; +//#[cfg(test)] +// mod test { +// use super::*; +// use crate::config::xml_ext::*; +// use maplit::hashmap; +//#[test] +// fn test_generic_generate() { +// let w_def1 = { +// let input = r#"{{nested1}}{{raw1}}"#; +// 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#""#; +// 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#""#; +// 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(); +// 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 ._. +//// TODO actually implement this test ._. - dbg!(&generic); - // panic!("REEEEEEEEEE") - } -} +// dbg!(&generic); +//// panic!("REEEEEEEEEE") +//} diff --git a/crates/simplexpr/src/ast.rs b/crates/simplexpr/src/ast.rs index db1aaf9..22bc15d 100644 --- a/crates/simplexpr/src/ast.rs +++ b/crates/simplexpr/src/ast.rs @@ -74,11 +74,16 @@ impl SimplExpr { Self::Literal(span, DynVal(s, Some(span))) } - /// Construct a synthetic simplexpr from a literal value, without adding any relevant span information (uses [DUMMY_SPAN]) - pub fn synth_literal(s: String) -> Self { + /// Construct a synthetic simplexpr from a literal string, without adding any relevant span information (uses [DUMMY_SPAN]) + pub fn synth_string(s: String) -> Self { Self::Literal(DUMMY_SPAN, DynVal(s, Some(DUMMY_SPAN))) } + /// Construct a synthetic simplexpr from a literal dynval, without adding any relevant span information (uses [DUMMY_SPAN]) + pub fn synth_literal>(s: T) -> Self { + Self::Literal(DUMMY_SPAN, s.into()) + } + pub fn span(&self) -> Span { match self { SimplExpr::Literal(span, _) => *span, diff --git a/crates/yuck/src/config/attributes.rs b/crates/yuck/src/config/attributes.rs index 34afc6b..e749c71 100644 --- a/crates/yuck/src/config/attributes.rs +++ b/crates/yuck/src/config/attributes.rs @@ -58,6 +58,7 @@ impl AttrEntry { } } +// TODO maybe make this generic over the contained content #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] pub struct Attributes { pub span: Span, diff --git a/crates/yuck/src/config/backend_window_options.rs b/crates/yuck/src/config/backend_window_options.rs index 0cc0a9e..1e61f16 100644 --- a/crates/yuck/src/config/backend_window_options.rs +++ b/crates/yuck/src/config/backend_window_options.rs @@ -21,7 +21,7 @@ pub type BackendWindowOptions = X11WindowOptions; pub struct X11WindowOptions { pub wm_ignore: bool, pub sticky: bool, - pub window_type: EwwWindowType, + pub window_type: WindowType, pub struts: StrutDefinition, } @@ -39,7 +39,7 @@ impl X11WindowOptions { } #[derive(Debug, Clone, PartialEq, Eq, smart_default::SmartDefault, serde::Serialize)] -pub enum EwwWindowType { +pub enum WindowType { #[default] Dock, Dialog, @@ -47,7 +47,7 @@ pub enum EwwWindowType { Normal, Utility, } -impl FromStr for EwwWindowType { +impl FromStr for WindowType { type Err = EnumParseError; fn from_str(s: &str) -> Result { diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs index 5940a93..5659563 100644 --- a/crates/yuck/src/config/script_var_definition.rs +++ b/crates/yuck/src/config/script_var_definition.rs @@ -32,7 +32,7 @@ pub enum VarSource { // TODO allow for other executors? (python, etc) Shell(String), #[serde(skip)] - Function(fn() -> Result>), + Function(fn() -> Result>), } #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct PollScriptVar { diff --git a/src/config/window_definition.rs b/src/config/window_definition.rs deleted file mode 100644 index e2c85ec..0000000 --- a/src/config/window_definition.rs +++ /dev/null @@ -1,167 +0,0 @@ -use super::{backend_window_options::*, *}; -use crate::{ensure_xml_tag_is, enum_parse, value::NumWithUnit, widgets::widget_node}; -use derive_more::*; -use serde::{Deserialize, Serialize}; -use smart_default::SmartDefault; -use std::{collections::HashMap, str::FromStr}; - -/// Full window-definition containing the fully expanded widget tree. -/// **Use this** rather than [RawEwwWindowDefinition]. -#[derive(Debug, Clone)] -pub struct EwwWindowDefinition { - pub name: WindowName, - - pub geometry: Option, - pub stacking: WindowStacking, - pub screen_number: Option, - pub widget: Box, - pub resizable: bool, - pub backend_options: BackendWindowOptions, -} - -impl EwwWindowDefinition { - pub fn generate(defs: &HashMap, window: RawEwwWindowDefinition) -> Result { - Ok(EwwWindowDefinition { - name: window.name, - geometry: window.geometry, - stacking: window.stacking, - screen_number: window.screen_number, - resizable: window.resizable, - widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?, - backend_options: window.backend_options, - }) - } -} - -/// Window-definition storing the raw WidgetUse, as received directly from parsing. -#[derive(Debug, Clone, PartialEq)] -pub struct RawEwwWindowDefinition { - pub name: WindowName, - pub geometry: Option, - pub stacking: WindowStacking, - pub widget: WidgetUse, - pub resizable: bool, - pub backend_options: BackendWindowOptions, - pub screen_number: Option, -} - -impl RawEwwWindowDefinition { - pub fn from_xml_element(xml: &XmlElement) -> Result { - ensure_xml_tag_is!(xml, "window"); - let geometry = match xml.child("geometry") { - Ok(node) => Some(EwwWindowGeometry::from_xml_element(node)?), - Err(_) => None, - }; - - Ok(RawEwwWindowDefinition { - name: WindowName(xml.attr("name")?), - geometry, - widget: WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?, - stacking: xml.parse_optional_attr("stacking")?.unwrap_or_default(), - // TODO maybe rename this to monitor? - screen_number: xml.parse_optional_attr("screen")?, - resizable: xml.parse_optional_attr("resizable")?.unwrap_or(true), - backend_options: BackendWindowOptions::from_xml_element(xml)?, - }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] -pub enum EwwWindowType { - #[default] - Dock, - Dialog, - Toolbar, - Normal, - Utility, -} -impl FromStr for EwwWindowType { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - enum_parse! { "window type", s, - "dock" => Self::Dock, - "toolbar" => Self::Toolbar, - "dialog" => Self::Dialog, - "normal" => Self::Normal, - "utility" => Self::Utility, - } - } -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)] -pub enum Side { - #[default] - Top, - Left, - Right, - Bottom, -} - -impl std::str::FromStr for Side { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - enum_parse! { "side", s, - "l" | "left" => Side::Left, - "r" | "right" => Side::Right, - "t" | "top" => Side::Top, - "b" | "bottom" => Side::Bottom, - } - } -} - -// Surface definition if the backend for X11 is enable -#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)] -pub struct StrutDefinition { - pub side: Side, - pub dist: NumWithUnit, -} - -impl StrutDefinition { - pub fn from_xml_element(xml: XmlElement) -> Result { - Ok(StrutDefinition { side: xml.attr("side")?.parse()?, dist: xml.attr("distance")?.parse()? }) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, SmartDefault)] -pub enum WindowStacking { - #[default] - Foreground, - Background, - Bottom, - Overlay, -} - -impl std::str::FromStr for WindowStacking { - type Err = anyhow::Error; - - #[cfg(not(feature = "wayland"))] - fn from_str(s: &str) -> Result { - enum_parse! { "WindowStacking", s, - "foreground" | "fg" | "f" => WindowStacking::Foreground, - "background" | "bg" | "b" => WindowStacking::Background, - } - } - - #[cfg(feature = "wayland")] - fn from_str(s: &str) -> Result { - enum_parse! { "WindowStacking", s, - "foreground" | "fg" => WindowStacking::Foreground, - "background" | "bg" => WindowStacking::Background, - "bottom" | "bt" => WindowStacking::Bottom, - "overlay" | "ov" => WindowStacking::Overlay, - } - } -} - -#[repr(transparent)] -#[derive(Clone, Hash, PartialEq, Eq, AsRef, FromStr, Display, Serialize, Deserialize, Default, From, DebugCustom)] -#[debug(fmt = "WindowName(\".0\")")] -pub struct WindowName(String); - -impl std::borrow::Borrow for WindowName { - fn borrow(&self) -> &str { - &self.0 - } -} diff --git a/src/value/primitive.rs b/src/value/primitive.rs deleted file mode 100644 index df49978..0000000 --- a/src/value/primitive.rs +++ /dev/null @@ -1,165 +0,0 @@ -use anyhow::*; -use itertools::Itertools; -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, fmt, iter::FromIterator}; - -#[derive(Clone, Deserialize, Serialize, derive_more::From, Default)] -pub struct PrimVal(pub String); - -impl fmt::Display for PrimVal { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} -impl fmt::Debug for PrimVal { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\"", self.0) - } -} - -/// Manually implement equality, to allow for values in different formats (i.e. "1" and "1.0") to still be considered as equal. -impl std::cmp::PartialEq for PrimVal { - fn eq(&self, other: &Self) -> bool { - if let (Ok(a), Ok(b)) = (self.as_f64(), other.as_f64()) { - a == b - } else { - self.0 == other.0 - } - } -} - -impl FromIterator for PrimVal { - fn from_iter>(iter: T) -> Self { - PrimVal(iter.into_iter().join("")) - } -} - -impl std::str::FromStr for PrimVal { - type Err = anyhow::Error; - - /// parses the value, trying to turn it into a number and a boolean first, - /// before deciding that it is a string. - fn from_str(s: &str) -> Result { - Ok(PrimVal::from_string(s.to_string())) - } -} - -macro_rules! impl_try_from { - (impl From<$typ:ty> { - $(for $for:ty => |$arg:ident| $code:expr);*; - }) => { - $(impl TryFrom<$typ> for $for { - type Error = anyhow::Error; - fn try_from($arg: $typ) -> Result { $code } - })* - }; -} -macro_rules! impl_primval_from { - ($($t:ty),*) => { - $(impl From<$t> for PrimVal { - fn from(x: $t) -> Self { PrimVal(x.to_string()) } - })* - }; -} - -impl_try_from!(impl From { - for String => |x| x.as_string(); - for f64 => |x| x.as_f64(); - for bool => |x| x.as_bool(); - for Vec => |x| x.as_vec(); -}); - -impl_primval_from!(bool, i32, u32, f32, u8, f64, &str); - -impl From<&serde_json::Value> for PrimVal { - fn from(v: &serde_json::Value) -> Self { - PrimVal( - v.as_str() - .map(|x| x.to_string()) - .or_else(|| serde_json::to_string(v).ok()) - .unwrap_or_else(|| "".to_string()), - ) - } -} - -impl PrimVal { - pub fn from_string(s: String) -> Self { - PrimVal(s) - } - - pub fn into_inner(self) -> String { - self.0 - } - - /// This will never fail - pub fn as_string(&self) -> Result { - Ok(self.0.to_owned()) - } - - pub fn as_f64(&self) -> Result { - self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e)) - } - - pub fn as_i32(&self) -> Result { - self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e)) - } - - pub fn as_bool(&self) -> Result { - self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e)) - } - - pub fn as_vec(&self) -> Result> { - parse_vec(self.0.to_owned()).map_err(|e| anyhow!("Couldn't convert {:#?} to a vec: {}", &self, e)) - } - - pub fn as_json_value(&self) -> Result { - serde_json::from_str::(&self.0) - .with_context(|| format!("Couldn't convert {:#?} to a json object", &self)) - } -} - -fn parse_vec(a: String) -> Result> { - match a.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { - Some(content) => { - let mut items: Vec = content.split(',').map(|x: &str| x.to_string()).collect(); - let mut removed = 0; - for times_ran in 0..items.len() { - // escapes `,` if there's a `\` before em - if items[times_ran - removed].ends_with('\\') { - items[times_ran - removed].pop(); - let it = items.remove((times_ran + 1) - removed); - items[times_ran - removed] += ","; - items[times_ran - removed] += ⁢ - removed += 1; - } - } - Ok(items) - } - None => Err(anyhow!("Is your array built like this: '[these,are,items]'?")), - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - #[test] - fn test_parse_vec() { - assert_eq!(vec![""], parse_vec("[]".to_string()).unwrap(), "should be able to parse empty lists"); - assert_eq!(vec!["hi"], parse_vec("[hi]".to_string()).unwrap(), "should be able to parse single element list"); - assert_eq!( - vec!["hi", "ho", "hu"], - parse_vec("[hi,ho,hu]".to_string()).unwrap(), - "should be able to parse three element list" - ); - assert_eq!(vec!["hi,ho"], parse_vec("[hi\\,ho]".to_string()).unwrap(), "should be able to parse list with escaped comma"); - assert_eq!( - vec!["hi,ho", "hu"], - parse_vec("[hi\\,ho,hu]".to_string()).unwrap(), - "should be able to parse two element list with escaped comma" - ); - assert!(parse_vec("".to_string()).is_err(), "Should fail when parsing empty string"); - assert!(parse_vec("[a,b".to_string()).is_err(), "Should fail when parsing unclosed list"); - assert!(parse_vec("a]".to_string()).is_err(), "Should fail when parsing unopened list"); - } -}