Start migration

This commit is contained in:
elkowar 2021-07-21 20:25:26 +02:00
parent 4f2e9cf063
commit 38f5307417
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
33 changed files with 149 additions and 3520 deletions

2043
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ use crate::{
config::{window_definition::WindowName, AnchorPoint}, config::{window_definition::WindowName, AnchorPoint},
display_backend, eww_state, display_backend, eww_state,
script_var_handler::*, script_var_handler::*,
value::{Coords, NumWithUnit, PrimVal, VarName}, dynval::{Coords, NumWithUnit, DynVal, VarName},
EwwPaths, EwwPaths,
}; };
use anyhow::*; use anyhow::*;
@ -38,7 +38,7 @@ pub type DaemonResponseReceiver = tokio::sync::mpsc::UnboundedReceiver<DaemonRes
#[derive(Debug)] #[derive(Debug)]
pub enum DaemonCommand { pub enum DaemonCommand {
NoOp, NoOp,
UpdateVars(Vec<(VarName, PrimVal)>), UpdateVars(Vec<(VarName, DynVal)>),
ReloadConfigAndCss(DaemonResponseSender), ReloadConfigAndCss(DaemonResponseSender),
UpdateConfig(config::EwwConfig), UpdateConfig(config::EwwConfig),
UpdateCss(String), UpdateCss(String),
@ -199,7 +199,7 @@ impl App {
gtk::main_quit(); gtk::main_quit();
} }
fn update_state(&mut self, fieldname: VarName, value: PrimVal) { fn update_state(&mut self, fieldname: VarName, value: DynVal) {
self.eww_state.update_variable(fieldname, value) self.eww_state.update_variable(fieldname, value)
} }

View file

@ -4,7 +4,7 @@ use regex::Regex;
use std::ops::Range; use std::ops::Range;
use crate::{ use crate::{
value::{AttrName, AttrVal}, dynval::{AttrName, AttrVal},
with_text_pos_context, with_text_pos_context,
}; };
use maplit::hashmap; use maplit::hashmap;

View file

@ -1,50 +1,56 @@
use anyhow::*; use anyhow::*;
use std::collections::HashMap; use std::collections::HashMap;
use yuck::{
use crate::{ config::{
util, script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition, window_definition::WindowDefinition,
value::{PrimVal, VarName}, },
parser::from_ast::FromAst,
value::VarName,
}; };
use super::{ use simplexpr::dynval::DynVal;
element::WidgetDefinition,
xml_ext::{XmlElement, XmlNode},
EwwWindowDefinition, RawEwwWindowDefinition, ScriptVar, WindowName,
};
use std::path::PathBuf; use std::path::PathBuf;
/// Eww configuration structure. /// Eww configuration structure.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EwwConfig { pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>, widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, EwwWindowDefinition>, windows: HashMap<String, WindowDefinition>,
initial_variables: HashMap<VarName, PrimVal>, initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVar>, script_vars: HashMap<VarName, ScriptVarDefinition>,
pub filepath: PathBuf, pub filepath: PathBuf,
} }
impl EwwConfig { impl EwwConfig {
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> { pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
Self::generate(RawEwwConfig::read_from_file(path)?) let content = std::fs::read_to_string(path)?;
let ast = yuck::parser::parse_string(0, &content)?;
let config = yuck::config::Config::from_ast(ast)?;
Self::generate(config)
} }
pub fn generate(conf: RawEwwConfig) -> Result<Self> { pub fn generate(config: yuck::config::Config) -> Result<Self> {
let RawEwwConfig { windows, initial_variables, script_vars, filepath, widgets } = conf;
Ok(EwwConfig { Ok(EwwConfig {
windows: windows windows: config
.window_definitions
.into_iter() .into_iter()
.map(|(name, window)| { .map(|(name, window)| {
Ok((name, EwwWindowDefinition::generate(&widgets, window).context("Failed expand window definition")?)) Ok((
name,
WindowDefinition::generate(&config.widget_definitions, window)
.context("Failed expand window definition")?,
))
}) })
.collect::<Result<HashMap<_, _>>>()?, .collect::<Result<HashMap<_, _>>>()?,
widgets, widgets: config.widget_definitions,
initial_variables, initial_variables: config.var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(),
script_vars, script_vars: config.script_vars,
filepath, filepath: todo!(),
}) })
} }
// TODO this is kinda ugly // TODO this is kinda ugly
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, PrimVal>> { pub fn generate_initial_state(&self) -> Result<HashMap<VarName, DynVal>> {
let mut vars = let mut vars =
self.script_vars.iter().map(|var| Ok((var.0.clone(), var.1.initial_value()?))).collect::<Result<HashMap<_, _>>>()?; self.script_vars.iter().map(|var| Ok((var.0.clone(), var.1.initial_value()?))).collect::<Result<HashMap<_, _>>>()?;
vars.extend(self.initial_variables.clone()); vars.extend(self.initial_variables.clone());
@ -68,222 +74,40 @@ impl EwwConfig {
} }
} }
/// Raw Eww configuration, before expanding widget usages. // Raw Eww configuration, before expanding widget usages.
#[derive(Debug, Clone)] //#[derive(Debug, Clone)]
pub struct RawEwwConfig { // pub struct RawEwwConfig {
widgets: HashMap<String, WidgetDefinition>, // widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, RawEwwWindowDefinition>, // windows: HashMap<WindowName, RawEwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimVal>, // initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVar>, // script_vars: HashMap<VarName, ScriptVar>,
pub filepath: PathBuf, // pub filepath: PathBuf,
} //}
impl RawEwwConfig { // impl RawEwwConfig {
pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec<RawEwwConfig>) -> Result<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| {
log::error!( // log::error!(
"{} '{}' defined twice (defined in {} and in {})", //"{} '{}' defined twice (defined in {} and in {})",
what, // what,
conflict, // conflict,
config_path.display(), // config_path.display(),
included_path.display() // included_path.display()
); //);
}; //};
for included_config in includes { // for included_config in includes {
for conflict in util::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 util::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 util::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 util::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)
} //}
} // Ok(eww_config)
Ok(eww_config) //}
}
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
let result: Result<_> = try {
let content = util::replace_env_var_references(std::fs::read_to_string(path.as_ref())?);
let content = content.replace("&", "&amp;");
let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!(e))?;
let root_node = XmlNode::from(document.root_element());
let root_element = root_node.as_element()?;
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()))?;
let parsed_includes = included_paths
.into_iter()
.map(Self::read_from_file)
.collect::<Result<Vec<_>>>()
.with_context(|| format!("Included in {}", path.as_ref().display()))?;
Self::merge_includes(config, parsed_includes)
.context("Failed to merge included files into parent configuration file")?
};
result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display()))
}
pub fn from_xml_element<P: AsRef<std::path::Path>>(xml: XmlElement, path: P) -> Result<(Self, Vec<PathBuf>)> {
let path = path.as_ref();
let included_paths = match xml.child("includes").ok() {
Some(tag) => tag
.child_elements()
.map(|child| {
crate::ensure_xml_tag_is!(child, "file");
Ok(util::join_path_pretty(path, PathBuf::from(child.attr("path")?)))
})
.collect::<Result<Vec<_>>>()?,
None => Default::default(),
};
let definitions = match xml.child("definitions").ok() {
Some(tag) => tag
.child_elements()
.map(|child| {
let def = WidgetDefinition::from_xml_element(&child).with_context(|| {
format!("Error parsing widget definition at {}:{}", path.display(), &child.text_pos())
})?;
Ok((def.name.clone(), def))
})
.collect::<Result<HashMap<_, _>>>()?,
None => Default::default(),
};
let windows = match xml.child("windows").ok() {
Some(tag) => tag
.child_elements()
.map(|child| {
let def = RawEwwWindowDefinition::from_xml_element(&child).with_context(|| {
format!("Error parsing window definition at {}:{}", path.display(), &child.text_pos())
})?;
Ok((def.name.to_owned(), def))
})
.collect::<Result<HashMap<_, _>>>()?,
None => Default::default(),
};
let (initial_variables, script_vars) = match xml.child("variables").ok() {
Some(tag) => parse_variables_block(tag)?,
None => Default::default(),
};
let config = RawEwwConfig { widgets: definitions, windows, initial_variables, script_vars, filepath: path.to_path_buf() };
Ok((config, included_paths))
}
}
fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimVal>, HashMap<VarName, ScriptVar>)> {
let mut normal_vars = HashMap::new();
let mut script_vars = HashMap::new();
for node in xml.child_elements() {
match node.tag_name() {
"var" => {
let value = node.only_child().map(|c| c.as_text_or_sourcecode()).unwrap_or_else(|_| String::new());
normal_vars.insert(VarName(node.attr("name")?.to_owned()), PrimVal::from_string(value));
}
"script-var" => {
let script_var = ScriptVar::from_xml_element(node)?;
script_vars.insert(script_var.name().clone(), script_var);
}
_ => bail!("Illegal element in variables block: {}", node.as_tag_string()),
}
}
// Extends the variables with the predefined variables
let inbuilt = crate::config::inbuilt::get_inbuilt_vars();
for i in util::extend_safe(&mut script_vars, inbuilt) {
log::error!(
"script-var '{}' defined twice (defined in your config and in the eww included variables)\nHint: don't define any \
varible like any of these: https://elkowar.github.io/eww/main/magic-variables-documenation/",
i,
);
}
Ok((normal_vars, script_vars))
}
#[cfg(test)]
mod test {
use crate::config::{RawEwwConfig, XmlNode};
use std::collections::HashMap;
#[test]
fn test_merge_includes() {
let input1 = r#"
<eww>
<definitions>
<def name="test1">
<box orientation="v">
{{var1}}
</box>
</def>
</definitions>
<variables>
<var name="var1">var1</var>
</variables>
<windows>
<window name="window1">
<size x="100" y="200" />
<pos x="100" y="200" />
<widget>
<test1 name="test2" />
</widget>
</window>
</windows>
</eww>
"#;
let input2 = r#"
<eww>
<definitions>
<def name="test2">
<box orientation="v">
{{var2}}
</box>
</def>
</definitions>
<variables>
<var name="var2">var2</var>
</variables>
<windows>
<window name="window2">
<size x="100" y="200" />
<pos x="100" y="200" />
<widget>
<test2 name="test2" />
</widget>
</window>
</windows>
</eww>
"#;
let document1 = roxmltree::Document::parse(&input1).unwrap();
let document2 = roxmltree::Document::parse(input2).unwrap();
let config1 =
RawEwwConfig::from_xml_element(XmlNode::from(document1.root_element()).as_element().unwrap().clone(), "").unwrap().0;
let config2 =
RawEwwConfig::from_xml_element(XmlNode::from(document2.root_element()).as_element().unwrap().clone(), "").unwrap().0;
let base_config = RawEwwConfig {
widgets: HashMap::new(),
windows: HashMap::new(),
initial_variables: HashMap::new(),
script_vars: HashMap::new(),
filepath: "test_path".into(),
};
let merged_config = RawEwwConfig::merge_includes(base_config, vec![config1, config2]).unwrap();
assert_eq!(merged_config.widgets.len(), 2);
assert_eq!(merged_config.windows.len(), 2);
assert_eq!(merged_config.initial_variables.len(), 2);
assert_eq!(merged_config.script_vars.len(), 6);
}
}

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
config::{system_stats::*, PollScriptVar, ScriptVar, VarSource}, config::{system_stats::*, PollScriptVar, ScriptVar, VarSource},
value::{PrimVal as PrimitiveValue, VarName}, dynval::{DynVal as PrimitiveValue, VarName},
}; };
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
@ -20,16 +20,16 @@ macro_rules! builtin_vars {
pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVar> { pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVar> {
builtin_vars! {Duration::new(2, 0), 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}}` // @desc EWW_TEMPS - Heat of the components in Celcius\nExample: `{{(CPU_TEMPS.core_1 + CPU_TEMPS.core_2) / 2}}`
"EWW_TEMPS" => || Ok(PrimitiveValue::from(cores())), "EWW_TEMPS" => || Ok(Primitivedynval::from(cores())),
// @desc EWW_RAM - The current RAM + Swap usage // @desc EWW_RAM - The current RAM + Swap usage
"EWW_RAM" => || Ok(PrimitiveValue::from(format!("{:.2}", ram()))), "EWW_RAM" => || Ok(Primitivedynval::from(format!("{:.2}", ram()))),
// @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs)\nExample: `{{EWW_DISK["/"]}}` // @desc EWW_DISK - Information on on all mounted partitions (Might report inaccurately on some filesystems, like btrfs)\nExample: `{{EWW_DISK["/"]}}`
"EWW_DISK" => || Ok(PrimitiveValue::from(disk())), "EWW_DISK" => || Ok(Primitivedynval::from(disk())),
// @desc EWW_BATTERY - Battery capacity in procent of the main battery // @desc EWW_BATTERY - Battery capacity in procent of the main battery
"EWW_BATTERY" => || Ok(PrimitiveValue::from( "EWW_BATTERY" => || Ok(Primitivedynval::from(
match get_battery_capacity() { match get_battery_capacity() {
Err(e) => { Err(e) => {
log::error!("Couldn't get the battery capacity: {:?}", e); log::error!("Couldn't get the battery capacity: {:?}", e);
@ -40,9 +40,9 @@ pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVar> {
)), )),
// @desc EWW_CPU_USAGE - Average CPU usage (all cores) since the last update (No MacOS support) // @desc EWW_CPU_USAGE - Average CPU usage (all cores) since the last update (No MacOS support)
"EWW_CPU_USAGE" => || Ok(PrimitiveValue::from(get_avg_cpu_usage())), "EWW_CPU_USAGE" => || Ok(Primitivedynval::from(get_avg_cpu_usage())),
// @desc EWW_NET - Bytes up/down on all interfaces // @desc EWW_NET - Bytes up/down on all interfaces
"EWW_NET" => || Ok(PrimitiveValue::from(net())), "EWW_NET" => || Ok(Primitivedynval::from(net())),
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
util, util,
value::{PrimVal, VarName}, dynval::{DynVal, VarName},
}; };
use anyhow::*; use anyhow::*;

View file

@ -9,7 +9,7 @@ use super::*;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum VarSource { pub enum VarSource {
Shell(String), Shell(String),
Function(fn() -> Result<PrimVal>), Function(fn() -> Result<DynVal>),
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct PollScriptVar { pub struct PollScriptVar {
@ -19,7 +19,7 @@ pub struct PollScriptVar {
} }
impl PollScriptVar { impl PollScriptVar {
pub fn run_once(&self) -> Result<PrimVal> { pub fn run_once(&self) -> Result<DynVal> {
match &self.command { match &self.command {
VarSource::Shell(x) => run_command(x), VarSource::Shell(x) => run_command(x),
VarSource::Function(x) => x(), VarSource::Function(x) => x(),
@ -47,7 +47,7 @@ impl ScriptVar {
} }
} }
pub fn initial_value(&self) -> Result<PrimVal> { pub fn initial_value(&self) -> Result<DynVal> {
match self { match self {
ScriptVar::Poll(x) => match &x.command { ScriptVar::Poll(x) => match &x.command {
VarSource::Function(f) => f().with_context(|| format!("Failed to compute initial value for {}", &self.name())), VarSource::Function(f) => f().with_context(|| format!("Failed to compute initial value for {}", &self.name())),
@ -55,7 +55,7 @@ impl ScriptVar {
run_command(f).with_context(|| format!("Failed to compute initial value for {}", &self.name())) run_command(f).with_context(|| format!("Failed to compute initial value for {}", &self.name()))
} }
}, },
ScriptVar::Tail(_) => Ok(PrimVal::from_string(String::new())), ScriptVar::Tail(_) => Ok(DynVal::from_string(String::new())),
} }
} }
@ -74,9 +74,9 @@ impl ScriptVar {
} }
/// Run a command and get the output /// Run a command and get the output
fn run_command(cmd: &str) -> Result<PrimVal> { fn run_command(cmd: &str) -> Result<DynVal> {
log::debug!("Running command: {}", cmd); log::debug!("Running command: {}", cmd);
let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?; let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?;
let output = output.trim_matches('\n'); let output = output.trim_matches('\n');
Ok(PrimVal::from(output)) Ok(DynVal::from(output))
} }

View file

@ -1,9 +1,14 @@
use super::*; use super::*;
use crate::{ensure_xml_tag_is, value::NumWithUnit, widgets::widget_node}; use crate::{dynval::NumWithUnit, ensure_xml_tag_is, widgets::widget_node};
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, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use yuck::config::{
backend_window_options::StrutDefinition,
window_definition::{WindowDefinition, WindowStacking},
window_geometry::WindowGeometry,
};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum EwwWindowType { pub enum EwwWindowType {
@ -36,11 +41,11 @@ impl Default for EwwWindowType {
/// **Use this** rather than `[RawEwwWindowDefinition]`. /// **Use this** rather than `[RawEwwWindowDefinition]`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EwwWindowDefinition { pub struct EwwWindowDefinition {
pub name: WindowName, pub name: String,
pub geometry: EwwWindowGeometry, pub geometry: WindowGeometry,
pub stacking: WindowStacking, pub stacking: WindowStacking,
pub screen_number: Option<i32>, pub monitor_number: Option<i32>,
pub widget: Box<dyn widget_node::WidgetNode>, pub widget: Box<dyn widget_node::WidgetNode>,
pub focusable: bool, pub focusable: bool,
@ -55,12 +60,12 @@ pub struct EwwWindowDefinition {
} }
impl EwwWindowDefinition { impl EwwWindowDefinition {
pub fn generate(defs: &HashMap<String, WidgetDefinition>, window: RawEwwWindowDefinition) -> Result<Self> { pub fn generate(defs: &HashMap<String, WidgetDefinition>, window: WindowDefinition) -> Result<Self> {
Ok(EwwWindowDefinition { Ok(EwwWindowDefinition {
name: window.name, name: window.name,
geometry: window.geometry, geometry: window.geometry,
stacking: window.stacking, stacking: window.stacking,
screen_number: window.screen_number, monitor_number: window.screen_number,
widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?, widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?,
focusable: window.focusable, focusable: window.focusable,
#[cfg(feature = "x11")] #[cfg(feature = "x11")]
@ -72,148 +77,3 @@ impl EwwWindowDefinition {
}) })
} }
} }
/// Window-definition storing the raw WidgetUse, as received directly from parsing.
#[derive(Debug, Clone, PartialEq)]
pub struct RawEwwWindowDefinition {
pub name: WindowName,
pub geometry: EwwWindowGeometry,
pub stacking: WindowStacking,
pub screen_number: Option<i32>,
pub widget: WidgetUse,
pub focusable: bool,
#[cfg(feature = "x11")]
pub window_type: EwwWindowType,
#[cfg(feature = "x11")]
pub struts: StrutDefinition,
#[cfg(feature = "wayland")]
pub exclusive: bool,
}
impl RawEwwWindowDefinition {
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
ensure_xml_tag_is!(xml, "window");
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
// TODO maybe rename this to monitor?
let focusable = xml.parse_optional_attr("focusable")?;
let screen_number = xml.parse_optional_attr("screen")?;
#[cfg(feature = "x11")]
let struts: Option<StrutDefinition> =
xml.child("reserve").ok().map(StrutDefinition::from_xml_element).transpose().context("Failed to parse <reserve>")?;
Ok(RawEwwWindowDefinition {
name: WindowName(xml.attr("name")?),
geometry: match xml.child("geometry") {
Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
Err(_) => EwwWindowGeometry::default(),
},
#[cfg(feature = "x11")]
window_type: match xml.attr("windowtype") {
Ok(v) => EwwWindowType::from_str(&v)?,
Err(_) => match struts {
Some(_) => EwwWindowType::Dock,
None => Default::default(),
},
},
widget: WidgetUse::from_xml_node(xml.child("widget")?.only_child()?)?,
stacking,
screen_number,
focusable: focusable.unwrap_or(false),
#[cfg(feature = "x11")]
struts: struts.unwrap_or_default(),
#[cfg(feature = "wayland")]
exclusive: xml.parse_optional_attr("exclusive")?.unwrap_or_default(),
})
}
}
#[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<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)),
}
}
}
// 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<Self> {
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<Self> {
let s = s.to_lowercase();
match s.as_str() {
"foreground" | "fg" | "f" => Ok(WindowStacking::Foreground),
"background" | "bg" | "b" => Ok(WindowStacking::Background),
_ => Err(anyhow!("Couldn't parse '{}' as window stacking, must be either foreground, fg, background or bg", s)),
}
}
#[cfg(feature = "wayland")]
fn from_str(s: &str) -> Result<Self> {
let s = s.to_lowercase();
match s.as_str() {
"foreground" | "fg" => Ok(WindowStacking::Foreground),
"background" | "bg" => Ok(WindowStacking::Background),
"bottom" | "bt" => Ok(WindowStacking::Bottom),
"overlay" | "ov" => Ok(WindowStacking::Overlay),
_ => Err(anyhow!(
"Couldn't parse '{}' as window stacking, must be either foreground, fg, background, bg, bottom, bt, overlay or \
ov",
s
)),
}
}
}
#[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<str> for WindowName {
fn borrow(&self) -> &str {
&self.0
}
}

View file

@ -1,4 +1,4 @@
use crate::value::Coords; use crate::dynval::Coords;
use anyhow::*; use anyhow::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smart_default::SmartDefault; use smart_default::SmartDefault;

View file

@ -1,16 +1,16 @@
use crate::{ use crate::{
config::window_definition::WindowName, config::window_definition::WindowName,
value::{AttrName, AttrValElement, VarName}, dynval::{AttrName, AttrValElement, VarName},
}; };
use anyhow::*; use anyhow::*;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use crate::value::{AttrVal, PrimVal}; use crate::dynval::{AttrVal, DynVal};
/// Handler that gets 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, PrimVal>) -> Result<()> + 'static>, func: Box<dyn Fn(HashMap<AttrName, DynVal>) -> Result<()> + 'static>,
unresolved_values: HashMap<AttrName, AttrVal>, unresolved_values: HashMap<AttrName, AttrVal>,
} }
@ -21,7 +21,7 @@ impl StateChangeHandler {
/// Run the StateChangeHandler. /// Run the StateChangeHandler.
/// [`state`] should be the global [EwwState::state]. /// [`state`] should be the global [EwwState::state].
fn run_with_state(&self, state: &HashMap<VarName, PrimVal>) { fn run_with_state(&self, state: &HashMap<VarName, DynVal>) {
let resolved_attrs = self let resolved_attrs = self
.unresolved_values .unresolved_values
.clone() .clone()
@ -61,7 +61,7 @@ impl EwwWindowState {
#[derive(Default)] #[derive(Default)]
pub struct EwwState { pub struct EwwState {
windows: HashMap<WindowName, EwwWindowState>, windows: HashMap<WindowName, EwwWindowState>,
variables_state: HashMap<VarName, PrimVal>, variables_state: HashMap<VarName, DynVal>,
} }
impl std::fmt::Debug for EwwState { impl std::fmt::Debug for EwwState {
@ -71,11 +71,11 @@ impl std::fmt::Debug for EwwState {
} }
impl EwwState { impl EwwState {
pub fn from_default_vars(defaults: HashMap<VarName, PrimVal>) -> Self { pub fn from_default_vars(defaults: HashMap<VarName, DynVal>) -> Self {
EwwState { variables_state: defaults, ..EwwState::default() } EwwState { variables_state: defaults, ..EwwState::default() }
} }
pub fn get_variables(&self) -> &HashMap<VarName, PrimVal> { pub fn get_variables(&self) -> &HashMap<VarName, DynVal> {
&self.variables_state &self.variables_state
} }
@ -91,7 +91,7 @@ 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: PrimVal) { pub fn update_variable(&mut self, key: VarName, value: DynVal) {
self.variables_state.insert(key.clone(), value); self.variables_state.insert(key.clone(), value);
// run all of the handlers // run all of the handlers
@ -103,12 +103,12 @@ impl EwwState {
} }
/// Look up a single variable in the eww state, returning an `Err` when the value is not found. /// Look up a single variable in the eww state, returning an `Err` when the value is not found.
pub fn lookup(&self, var_name: &VarName) -> Result<&PrimVal> { pub fn lookup(&self, var_name: &VarName) -> Result<&DynVal> {
self.variables_state.get(var_name).with_context(|| format!("Unknown variable '{}' referenced", var_name)) self.variables_state.get(var_name).with_context(|| format!("Unknown variable '{}' referenced", var_name))
} }
/// resolves a value if possible, using the current eww_state. /// resolves a value if possible, using the current eww_state.
pub fn resolve_once<'a>(&'a self, value: &'a AttrVal) -> Result<PrimVal> { pub fn resolve_once<'a>(&'a self, value: &'a AttrVal) -> Result<DynVal> {
value value
.iter() .iter()
.map(|element| match element { .map(|element| match element {
@ -120,7 +120,7 @@ impl EwwState {
/// 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. /// values to it's gtk widget.
pub fn resolve<F: Fn(HashMap<AttrName, PrimVal>) -> Result<()> + 'static + Clone>( pub fn resolve<F: Fn(HashMap<AttrName, DynVal>) -> Result<()> + 'static + Clone>(
&mut self, &mut self,
window_name: &WindowName, window_name: &WindowName,
required_attributes: HashMap<AttrName, AttrVal>, required_attributes: HashMap<AttrName, AttrVal>,

View file

@ -29,7 +29,6 @@ pub mod opts;
pub mod script_var_handler; pub mod script_var_handler;
pub mod server; pub mod server;
pub mod util; pub mod util;
pub mod value;
pub mod widgets; pub mod widgets;
fn main() { fn main() {

View file

@ -5,7 +5,7 @@ use structopt::StructOpt;
use crate::{ use crate::{
app, app,
config::{AnchorPoint, WindowName}, config::{AnchorPoint, WindowName},
value::{Coords, PrimVal, VarName}, dynval::{Coords, DynVal, VarName},
}; };
/// Struct that gets generated from `RawOpt`. /// Struct that gets generated from `RawOpt`.
@ -61,7 +61,7 @@ pub enum ActionWithServer {
Update { Update {
/// variable_name="new_value"-pairs that will be updated /// variable_name="new_value"-pairs that will be updated
#[structopt(parse(try_from_str = parse_var_update_arg))] #[structopt(parse(try_from_str = parse_var_update_arg))]
mappings: Vec<(VarName, PrimVal)>, mappings: Vec<(VarName, DynVal)>,
}, },
/// open a window /// open a window
@ -142,11 +142,11 @@ impl From<RawOpt> for Opt {
} }
} }
fn parse_var_update_arg(s: &str) -> Result<(VarName, PrimVal)> { fn parse_var_update_arg(s: &str) -> Result<(VarName, DynVal)> {
let (name, value) = s let (name, value) = s
.split_once('=') .split_once('=')
.with_context(|| format!("arguments must be in the shape `variable_name=\"new_value\"`, but got: {}", s))?; .with_context(|| format!("arguments must be in the shape `variable_name=\"new_value\"`, but got: {}", s))?;
Ok((name.into(), PrimVal::from_string(value.to_owned()))) Ok((name.into(), DynVal::from_string(value.to_owned())))
} }
impl ActionWithServer { impl ActionWithServer {

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{ use crate::{
app, config, app, config,
value::{PrimVal, VarName}, dynval::{DynVal, VarName},
}; };
use anyhow::*; use anyhow::*;
use app::DaemonCommand; use app::DaemonCommand;
@ -197,7 +197,7 @@ impl TailVarHandler {
_ = handle.wait() => break, _ = handle.wait() => break,
_ = cancellation_token.cancelled() => break, _ = cancellation_token.cancelled() => break,
Ok(Some(line)) = stdout_lines.next_line() => { Ok(Some(line)) = stdout_lines.next_line() => {
let new_value = PrimVal::from_string(line.to_owned()); let new_value = DynVal::from_string(line.to_owned());
evt_send.send(DaemonCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?; evt_send.send(DaemonCommand::UpdateVars(vec![(var.name.to_owned(), new_value)]))?;
} }
else => break, else => break,

View file

@ -1,209 +0,0 @@
use anyhow::*;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, iter::FromIterator};
use super::super::*;
/// A value assigned to an attribute in a widget.
/// This can be a primitive String that contains any amount of variable
/// references, as would be generated by the string "foo {{var}} bar".
#[derive(Serialize, Deserialize, Clone, PartialEq, derive_more::Into, derive_more::From, Default)]
pub struct AttrVal(Vec<AttrValElement>);
impl fmt::Display for AttrVal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.iter().map(|x| format!("{}", x)).join(""))
}
}
impl fmt::Debug for AttrVal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AttrValue({:?})", self.0)
}
}
impl IntoIterator for AttrVal {
type IntoIter = std::vec::IntoIter<Self::Item>;
type Item = AttrValElement;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl FromIterator<AttrValElement> for AttrVal {
fn from_iter<T: IntoIterator<Item = AttrValElement>>(iter: T) -> Self {
AttrVal(iter.into_iter().collect())
}
}
impl AttrVal {
pub fn from_primitive<T: Into<PrimVal>>(v: T) -> Self {
AttrVal(vec![AttrValElement::Primitive(v.into())])
}
pub fn from_var_ref<T: Into<VarName>>(v: T) -> Self {
AttrVal(vec![AttrValElement::Expr(AttrValExpr::VarRef(v.into()))])
}
pub fn iter(&self) -> std::slice::Iter<AttrValElement> {
self.0.iter()
}
pub fn var_refs(&self) -> impl Iterator<Item = &VarName> {
self.0.iter().filter_map(|x| Some(x.as_expr()?.var_refs())).flatten()
}
/// 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, AttrVal>) -> AttrVal {
self.into_iter()
.map(|entry| match entry {
AttrValElement::Expr(expr) => AttrValElement::Expr(expr.map_terminals_into(|child_expr| match child_expr {
AttrValExpr::VarRef(var_name) => match variables.get(&var_name) {
Some(value) => AttrValExpr::Literal(value.clone()),
None => AttrValExpr::VarRef(var_name),
},
other => other,
})),
_ => entry,
})
.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, PrimVal>) -> Result<PrimVal> {
self.into_iter()
.map(|element| match element {
AttrValElement::Primitive(x) => Ok(x),
AttrValElement::Expr(expr) => expr.eval(variables),
})
.collect()
}
// TODO this could be a fancy Iterator implementation, ig
pub fn parse_string(s: &str) -> AttrVal {
let mut elements = Vec::new();
let mut cur_word = "".to_owned();
let mut cur_varref: Option<String> = None;
let mut curly_count = 0;
for c in s.chars() {
if let Some(ref mut varref) = cur_varref {
if c == '}' {
curly_count -= 1;
if curly_count == 0 {
elements.push(AttrValElement::Expr(AttrValExpr::parse(varref).unwrap()));
cur_varref = None
}
} else {
curly_count = 2;
varref.push(c);
}
} else if c == '{' {
curly_count += 1;
if curly_count == 2 {
if !cur_word.is_empty() {
elements.push(AttrValElement::primitive(std::mem::take(&mut cur_word)));
}
cur_varref = Some(String::new())
}
} else {
if curly_count == 1 {
cur_word.push('{');
}
curly_count = 0;
cur_word.push(c);
}
}
if let Some(unfinished_varref) = cur_varref.take() {
elements.push(AttrValElement::primitive(unfinished_varref));
} else if !cur_word.is_empty() {
elements.push(AttrValElement::primitive(cur_word));
}
AttrVal(elements)
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub enum AttrValElement {
Primitive(PrimVal),
Expr(AttrValExpr),
}
impl fmt::Display for AttrValElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AttrValElement::Primitive(x) => write!(f, "{}", x),
AttrValElement::Expr(x) => write!(f, "{{{{{}}}}}", x),
}
}
}
impl fmt::Debug for AttrValElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AttrValElement::Primitive(x) => write!(f, "Primitive({:?})", x),
AttrValElement::Expr(x) => write!(f, "Expr({:?})", x),
}
}
}
impl AttrValElement {
pub fn primitive(s: String) -> Self {
AttrValElement::Primitive(PrimVal::from_string(s))
}
pub fn as_expr(&self) -> Option<&AttrValExpr> {
match self {
AttrValElement::Expr(x) => Some(x),
_ => None,
}
}
pub fn as_primitive(&self) -> Option<&PrimVal> {
match self {
AttrValElement::Primitive(x) => Some(x),
_ => None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_string_or_var_ref_list() {
let input = "{{foo}}{{bar}}b{}azb{a}z{{bat}}{}quok{{test}}";
let output = AttrVal::parse_string(input);
assert_eq!(
output,
AttrVal(vec![
AttrValElement::Expr(AttrValExpr::VarRef(VarName("foo".to_owned()))),
AttrValElement::Expr(AttrValExpr::VarRef(VarName("bar".to_owned()))),
AttrValElement::primitive("b{}azb{a}z".to_owned()),
AttrValElement::Expr(AttrValExpr::VarRef(VarName("bat".to_owned()))),
AttrValElement::primitive("{}quok".to_owned()),
AttrValElement::Expr(AttrValExpr::VarRef(VarName("test".to_owned()))),
]),
)
}
#[test]
fn test_parse_string_with_var_refs_attr_value() {
assert_eq!(
AttrVal(
vec![
AttrValElement::Expr(AttrValExpr::VarRef(VarName("var".to_owned()))),
AttrValElement::primitive("something".to_owned())
]
.into()
),
AttrVal::parse_string("{{var}}something")
);
}
}

View file

@ -1,244 +0,0 @@
use super::super::*;
use anyhow::*;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum BinOp {
Plus,
Minus,
Times,
Div,
Mod,
Equals,
NotEquals,
And,
Or,
GT,
LT,
Elvis,
RegexMatch,
}
impl std::fmt::Display for BinOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinOp::Plus => write!(f, "+"),
BinOp::Minus => write!(f, "-"),
BinOp::Times => write!(f, "*"),
BinOp::Div => write!(f, "/"),
BinOp::Mod => write!(f, "%"),
BinOp::Equals => write!(f, "=="),
BinOp::NotEquals => write!(f, "!="),
BinOp::And => write!(f, "&&"),
BinOp::Or => write!(f, "||"),
BinOp::GT => write!(f, ">"),
BinOp::LT => write!(f, "<"),
BinOp::Elvis => write!(f, "?:"),
BinOp::RegexMatch => write!(f, "=~"),
}
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum UnaryOp {
Not,
}
impl std::fmt::Display for UnaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryOp::Not => write!(f, "!"),
}
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum AttrValExpr {
Literal(AttrVal),
VarRef(VarName),
BinOp(Box<AttrValExpr>, BinOp, Box<AttrValExpr>),
UnaryOp(UnaryOp, Box<AttrValExpr>),
IfElse(Box<AttrValExpr>, Box<AttrValExpr>, Box<AttrValExpr>),
JsonAccess(Box<AttrValExpr>, Box<AttrValExpr>),
FunctionCall(String, Vec<AttrValExpr>),
}
impl std::fmt::Display for AttrValExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AttrValExpr::VarRef(x) => write!(f, "{}", x),
AttrValExpr::Literal(x) => write!(f, "\"{}\"", x),
AttrValExpr::BinOp(l, op, r) => write!(f, "({} {} {})", l, op, r),
AttrValExpr::UnaryOp(op, x) => write!(f, "{}{}", op, x),
AttrValExpr::IfElse(a, b, c) => write!(f, "(if {} then {} else {})", a, b, c),
AttrValExpr::JsonAccess(value, index) => write!(f, "{}[{}]", value, index),
AttrValExpr::FunctionCall(function_name, args) => write!(f, "{}({})", function_name, args.iter().join(", ")),
}
}
}
// impl std::fmt::Debug for AttrValueExpr {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{:?}", self)
//}
impl AttrValExpr {
pub fn map_terminals_into(self, f: impl Fn(Self) -> Self) -> Self {
use AttrValExpr::*;
match self {
BinOp(box a, op, box b) => BinOp(box f(a), op, box f(b)),
IfElse(box a, box b, box c) => IfElse(box f(a), box f(b), box f(c)),
other => f(other),
}
}
/// resolve variable references in the expression. Fails if a variable cannot be resolved.
pub fn resolve_refs(self, variables: &HashMap<VarName, PrimVal>) -> Result<Self> {
use AttrValExpr::*;
match self {
// Literal(x) => Ok(Literal(AttrValue::from_primitive(x.resolve_fully(&variables)?))),
Literal(x) => Ok(Literal(x)),
VarRef(ref name) => Ok(Literal(AttrVal::from_primitive(
variables.get(name).with_context(|| format!("Unknown variable {} referenced in {:?}", &name, &self))?.clone(),
))),
BinOp(box a, op, box b) => Ok(BinOp(box a.resolve_refs(variables)?, op, box b.resolve_refs(variables)?)),
UnaryOp(op, box x) => Ok(UnaryOp(op, box x.resolve_refs(variables)?)),
IfElse(box a, box b, box c) => {
Ok(IfElse(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?, box c.resolve_refs(variables)?))
}
JsonAccess(box a, box b) => Ok(JsonAccess(box a.resolve_refs(variables)?, box b.resolve_refs(variables)?)),
FunctionCall(function_name, args) => {
Ok(FunctionCall(function_name, args.into_iter().map(|a| a.resolve_refs(variables)).collect::<Result<_>>()?))
}
}
}
pub fn var_refs(&self) -> Vec<&VarName> {
use AttrValExpr::*;
match self {
Literal(s) => s.var_refs().collect(),
VarRef(name) => vec![name],
BinOp(box a, _, box b) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs
}
UnaryOp(_, box x) => x.var_refs(),
IfElse(box a, box b, box c) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs.append(&mut c.var_refs());
refs
}
JsonAccess(box a, box b) => {
let mut refs = a.var_refs();
refs.append(&mut b.var_refs());
refs
}
FunctionCall(_, args) => args.iter().flat_map(|a| a.var_refs()).collect_vec(),
}
}
pub fn eval(self, values: &HashMap<VarName, PrimVal>) -> Result<PrimVal> {
match self {
AttrValExpr::Literal(x) => x.resolve_fully(values),
AttrValExpr::VarRef(ref name) => values
.get(name)
.cloned()
.context(format!("Got unresolved variable {} while trying to evaluate expression {:?}", &name, &self)),
AttrValExpr::BinOp(a, op, b) => {
let a = a.eval(values)?;
let b = b.eval(values)?;
Ok(match op {
BinOp::Equals => PrimVal::from(a == b),
BinOp::NotEquals => PrimVal::from(a != b),
BinOp::And => PrimVal::from(a.as_bool()? && b.as_bool()?),
BinOp::Or => PrimVal::from(a.as_bool()? || b.as_bool()?),
BinOp::Plus => PrimVal::from(a.as_f64()? + b.as_f64()?),
BinOp::Minus => PrimVal::from(a.as_f64()? - b.as_f64()?),
BinOp::Times => PrimVal::from(a.as_f64()? * b.as_f64()?),
BinOp::Div => PrimVal::from(a.as_f64()? / b.as_f64()?),
BinOp::Mod => PrimVal::from(a.as_f64()? % b.as_f64()?),
BinOp::GT => PrimVal::from(a.as_f64()? > b.as_f64()?),
BinOp::LT => PrimVal::from(a.as_f64()? < b.as_f64()?),
BinOp::Elvis => PrimVal::from(if a.0.is_empty() { b } else { a }),
BinOp::RegexMatch => {
let regex = regex::Regex::new(&b.as_string()?)?;
PrimVal::from(regex.is_match(&a.as_string()?))
}
})
}
AttrValExpr::UnaryOp(op, a) => {
let a = a.eval(values)?;
Ok(match op {
UnaryOp::Not => PrimVal::from(!a.as_bool()?),
})
}
AttrValExpr::IfElse(cond, yes, no) => {
if cond.eval(values)?.as_bool()? {
yes.eval(values)
} else {
no.eval(values)
}
}
AttrValExpr::JsonAccess(val, index) => {
let val = val.eval(values)?;
let index = index.eval(values)?;
match val.as_json_value()? {
serde_json::Value::Array(val) => {
let index = index.as_i32()?;
let indexed_value = val.get(index as usize).unwrap_or(&serde_json::Value::Null);
Ok(PrimVal::from(indexed_value))
}
serde_json::Value::Object(val) => {
let indexed_value = val
.get(&index.as_string()?)
.or_else(|| val.get(&index.as_i32().ok()?.to_string()))
.unwrap_or(&serde_json::Value::Null);
Ok(PrimVal::from(indexed_value))
}
_ => bail!("Unable to index into value {}", val),
}
}
AttrValExpr::FunctionCall(function_name, args) => {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_>>()?;
call_expr_function(&function_name, args)
}
}
}
pub fn parse(s: &str) -> Result<Self> {
let parsed = match parser::parse(s) {
Ok((_, x)) => Ok(x),
Err(nom::Err::Error(e) | nom::Err::Failure(e)) => Err(anyhow!(nom::error::convert_error(s, e))),
Err(nom::Err::Incomplete(_)) => Err(anyhow!("Parsing incomplete")),
};
parsed.context("Failed to parse expression")
}
}
fn call_expr_function(name: &str, args: Vec<PrimVal>) -> Result<PrimVal> {
match name {
"round" => match args.as_slice() {
[num, digits] => {
let num = num.as_f64()?;
let digits = digits.as_i32()?;
Ok(PrimVal::from(format!("{:.1$}", num, digits as usize)))
}
_ => Err(anyhow!("Incorrect number of arguments given to {}", name)),
},
"replace" => match args.as_slice() {
[string, pattern, replacement] => {
let string = string.as_string()?;
let pattern = regex::Regex::new(&pattern.as_string()?)?;
let replacement = replacement.as_string()?;
Ok(PrimVal::from(pattern.replace_all(&string, replacement.replace("$", "$$").replace("\\", "$")).into_owned()))
}
_ => Err(anyhow!("Incorrect number of arguments given to {}", name)),
},
_ => Err(anyhow!("Unknown function {}", name)),
}
}

View file

@ -1,5 +0,0 @@
pub mod attr_value;
pub mod attr_value_expr;
pub mod parser;
pub use attr_value::*;
pub use attr_value_expr::*;

View file

@ -1,224 +0,0 @@
use super::*;
use nom::{
branch::*,
bytes::complete::{tag, take_while},
character::complete::{multispace0 as multispace, *},
combinator::{map, map_res, *},
error::{context, ParseError, VerboseError},
multi::{many0, separated_list0},
sequence::{delimited, preceded, *},
IResult, Parser,
};
use super::super::*;
fn ws<'a, P, O, E: ParseError<&'a str>>(p: P) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
P: Parser<&'a str, O, E>,
{
delimited(multispace, p, multispace)
}
fn parse_num(i: &str) -> IResult<&str, f64, VerboseError<&str>> {
let (i, neg) = opt(tag("-"))(i)?;
let (i, num): (_, f64) = map_res(take_while(|c: char| c.is_numeric() || c == '.'), |n: &str| n.parse::<f64>())(i)?;
Ok((i, if neg.is_some() { -num } else { num }))
}
fn parse_bool(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
alt((tag("true"), tag("false")))(i)
}
fn parse_literal(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
alt((parse_bool, parse_stringlit, recognize(parse_num)))(i)
}
fn parse_stringlit(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
alt((delimited(tag("'"), take_while(|c| c != '\''), tag("'")), delimited(tag("\""), take_while(|c| c != '"'), tag("\""))))(i)
}
fn parse_identifier(i: &str) -> IResult<&str, &str, VerboseError<&str>> {
verify(recognize(pair(alt((alpha1, tag("_"), tag("-"))), many0(alt((alphanumeric1, tag("_"), tag("-")))))), |x| {
!["if", "then", "else"].contains(x)
})(i)
}
fn parse_unary_op(i: &str) -> IResult<&str, UnaryOp, VerboseError<&str>> {
value(UnaryOp::Not, tag("!"))(i)
}
fn parse_function_call(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, name) = take_while(|c: char| c.is_ascii_alphanumeric() || c == '_')(i)?;
let (i, args) = delimited(tag("("), separated_list0(tag(","), ws(parse_expr)), tag(")"))(i)?;
Ok((i, AttrValExpr::FunctionCall(name.to_string(), args)))
}
/////////////////
// actual tree //
/////////////////
fn parse_factor(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, unary_op) = opt(parse_unary_op)(i)?;
let (i, factor) = alt((
context("expression", ws(delimited(tag("("), parse_expr, tag(")")))),
context("if-expression", ws(parse_ifelse)),
context("function-call", ws(parse_function_call)),
context("literal", map(ws(parse_literal), |x| AttrValExpr::Literal(AttrVal::parse_string(x)))),
context("identifier", map(ws(parse_identifier), |x| AttrValExpr::VarRef(VarName(x.to_string())))),
))(i)?;
Ok((
i,
match unary_op {
Some(op) => AttrValExpr::UnaryOp(op, box factor),
None => factor,
},
))
}
fn parse_object_index(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, initial) = parse_factor(i)?;
let (i, remainder) = many0(alt((
delimited(tag("["), ws(parse_expr), tag("]")),
map(preceded(tag("."), parse_identifier), |x| AttrValExpr::Literal(AttrVal::from_primitive(x))),
)))(i)?;
let indexes = remainder.into_iter().fold(initial, |acc, index| AttrValExpr::JsonAccess(box acc, box index));
Ok((i, indexes))
}
fn parse_term3(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, initial) = parse_object_index(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("*"), parse_object_index), |x| (BinOp::Times, x)),
map(preceded(tag("/"), parse_object_index), |x| (BinOp::Div, x)),
map(preceded(tag("%"), parse_object_index), |x| (BinOp::Mod, x)),
)))(i)?;
let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValExpr::BinOp(box acc, op, box expr));
Ok((i, exprs))
}
fn parse_term2(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, initial) = parse_term3(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("+"), parse_term3), |x| (BinOp::Plus, x)),
map(preceded(tag("-"), parse_term3), |x| (BinOp::Minus, x)),
)))(i)?;
let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValExpr::BinOp(box acc, op, box expr));
Ok((i, exprs))
}
fn parse_term1(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, initial) = parse_term2(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("=="), parse_term2), |x| (BinOp::Equals, x)),
map(preceded(tag("!="), parse_term2), |x| (BinOp::NotEquals, x)),
map(preceded(tag(">"), parse_term2), |x| (BinOp::GT, x)),
map(preceded(tag("<"), parse_term2), |x| (BinOp::LT, x)),
map(preceded(tag("=~"), parse_term2), |x| (BinOp::RegexMatch, x)),
)))(i)?;
let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValExpr::BinOp(box acc, op, box expr));
Ok((i, exprs))
}
pub fn parse_expr(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, initial) = parse_term1(i)?;
let (i, remainder) = many0(alt((
map(preceded(tag("&&"), parse_term1), |x| (BinOp::And, x)),
map(preceded(tag("||"), parse_term1), |x| (BinOp::Or, x)),
map(preceded(tag("?:"), parse_term1), |x| (BinOp::Elvis, x)),
)))(i)?;
let exprs = remainder.into_iter().fold(initial, |acc, (op, expr)| AttrValExpr::BinOp(box acc, op, box expr));
Ok((i, exprs))
}
fn parse_ifelse(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
let (i, _) = tag("if")(i)?;
let (i, a) = context("condition", ws(parse_expr))(i)?;
let (i, _) = tag("then")(i)?;
let (i, b) = context("true-case", ws(parse_expr))(i)?;
let (i, _) = tag("else")(i)?;
let (i, c) = context("false-case", ws(parse_expr))(i)?;
Ok((i, AttrValExpr::IfElse(box a, box b, box c)))
}
pub fn parse(i: &str) -> IResult<&str, AttrValExpr, VerboseError<&str>> {
complete(parse_expr)(i)
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parser() {
use self::{BinOp::*, UnaryOp::*};
use AttrValExpr::*;
assert_eq!(("", 12.22f64), parse_num("12.22").unwrap());
assert_eq!(Literal(AttrVal::from_primitive("12")), AttrValExpr::parse("12").unwrap());
assert_eq!(UnaryOp(Not, box Literal(AttrVal::from_primitive("false"))), AttrValExpr::parse("!false").unwrap());
assert_eq!(
BinOp(box Literal(AttrVal::from_primitive("12")), Plus, box Literal(AttrVal::from_primitive("2"))),
AttrValExpr::parse("12 + 2").unwrap()
);
assert_eq!(
BinOp(
box FunctionCall(
"test".to_string(),
vec![
JsonAccess(box VarRef(VarName("foo".to_string())), box Literal(AttrVal::from_primitive("hi"))),
Literal(AttrVal::from_primitive("ho")),
]
),
Times,
box Literal(AttrVal::from_primitive(2))
),
AttrValExpr::parse(r#"(test(foo["hi"], ("ho")) * 2)"#).unwrap()
);
assert_eq!(
UnaryOp(Not, box BinOp(box Literal(AttrVal::from_primitive("1")), Equals, box Literal(AttrVal::from_primitive("2")))),
AttrValExpr::parse("!(1 == 2)").unwrap()
);
assert_eq!(
IfElse(
box VarRef(VarName("a".to_string())),
box VarRef(VarName("b".to_string())),
box VarRef(VarName("c".to_string())),
),
AttrValExpr::parse("if a then b else c").unwrap()
);
assert_eq!(
JsonAccess(
box VarRef(VarName("array".to_string())),
box BinOp(box Literal(AttrVal::from_primitive("1")), Plus, box Literal(AttrVal::from_primitive("2")))
),
AttrValExpr::parse(r#"(array)[1+2]"#).unwrap()
);
assert_eq!(
JsonAccess(
box JsonAccess(
box VarRef(VarName("object".to_string())),
box Literal(AttrVal::from_primitive("field".to_string())),
),
box Literal(AttrVal::from_primitive("field2".to_string())),
),
AttrValExpr::parse(r#"object.field.field2"#).unwrap()
);
}
#[test]
fn test_complex() {
let parsed =
AttrValExpr::parse(r#"if hi > 12 + 2 * 2 && 12 == 15 then "foo" else if !true then 'hi' else "{{bruh}}""#).unwrap();
assert_eq!(
r#"(if ((hi > ("12" + ("2" * "2"))) && ("12" == "15")) then "foo" else (if !"true" then "hi" else "{{bruh}}"))"#,
format!("{}", parsed),
)
}
}

View file

@ -1,107 +0,0 @@
use anyhow::*;
use derive_more::*;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::{fmt, str::FromStr};
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, DebugCustom, SmartDefault)]
pub enum NumWithUnit {
#[display(fmt = "{}%", .0)]
#[debug(fmt = "{}%", .0)]
Percent(i32),
#[display(fmt = "{}px", .0)]
#[debug(fmt = "{}px", .0)]
#[default]
Pixels(i32),
}
impl NumWithUnit {
pub fn relative_to(&self, max: i32) -> i32 {
match *self {
NumWithUnit::Percent(n) => ((max as f64 / 100.0) * n as f64) as i32,
NumWithUnit::Pixels(n) => n,
}
}
}
impl FromStr for NumWithUnit {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static::lazy_static! {
static ref PATTERN: regex::Regex = regex::Regex::new("^(-?\\d+)(.*)$").unwrap();
};
let captures = PATTERN.captures(s).with_context(|| format!("could not parse '{}'", s))?;
let value = captures.get(1).unwrap().as_str().parse::<i32>()?;
let value = match captures.get(2).unwrap().as_str() {
"px" | "" => NumWithUnit::Pixels(value),
"%" => NumWithUnit::Percent(value),
_ => bail!("couldn't parse {}, unit must be either px or %", s),
};
Ok(value)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Display, Default)]
#[display(fmt = "{}*{}", x, y)]
pub struct Coords {
pub x: NumWithUnit,
pub y: NumWithUnit,
}
impl FromStr for Coords {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (x, y) = s
.split_once(|x: char| x.to_ascii_lowercase() == 'x' || x.to_ascii_lowercase() == '*')
.ok_or_else(|| anyhow!("must be formatted like 200x500"))?;
Coords::from_strs(x, y)
}
}
impl fmt::Debug for Coords {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CoordsWithUnits({}, {})", self.x, self.y)
}
}
impl Coords {
pub fn from_pixels(x: i32, y: i32) -> Self {
Coords { x: NumWithUnit::Pixels(x), y: NumWithUnit::Pixels(y) }
}
/// parse a string for x and a string for y into a [`Coords`] object.
pub fn from_strs(x: &str, y: &str) -> Result<Coords> {
Ok(Coords {
x: x.parse().with_context(|| format!("Failed to parse '{}'", x))?,
y: y.parse().with_context(|| format!("Failed to parse '{}'", y))?,
})
}
/// resolve the possibly relative coordinates relative to a given containers size
pub fn relative_to(&self, width: i32, height: i32) -> (i32, i32) {
(self.x.relative_to(width), self.y.relative_to(height))
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_num_with_unit() {
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55").unwrap());
assert_eq!(NumWithUnit::Pixels(55), NumWithUnit::from_str("55px").unwrap());
assert_eq!(NumWithUnit::Percent(55), NumWithUnit::from_str("55%").unwrap());
assert!(NumWithUnit::from_str("55pp").is_err());
}
#[test]
fn test_parse_coords() {
assert_eq!(Coords { x: NumWithUnit::Pixels(50), y: NumWithUnit::Pixels(60) }, Coords::from_str("50x60").unwrap());
assert!(Coords::from_str("5060").is_err());
}
}

View file

@ -1,46 +0,0 @@
use derive_more::*;
use serde::{Deserialize, Serialize};
pub mod attr_value;
pub mod coords;
pub mod primitive;
pub use attr_value::*;
pub use attr_value_expr::*;
pub use coords::*;
pub use primitive::*;
/// The name of a variable
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt = "VarName({})", .0)]
pub struct VarName(pub String);
impl std::borrow::Borrow<str> for VarName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for VarName {
fn from(s: &str) -> Self {
VarName(s.to_owned())
}
}
/// The name of an attribute
#[repr(transparent)]
#[derive(Clone, Hash, PartialEq, Eq, Serialize, Deserialize, AsRef, From, FromStr, Display, DebugCustom)]
#[debug(fmt="AttrName({})", .0)]
pub struct AttrName(pub String);
impl std::borrow::Borrow<str> for AttrName {
fn borrow(&self) -> &str {
&self.0
}
}
impl From<&str> for AttrName {
fn from(s: &str) -> Self {
AttrName(s.to_owned())
}
}

View file

@ -1,188 +0,0 @@
use anyhow::*;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt, iter::FromIterator};
use crate::impl_try_from;
#[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<Self> 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<PrimVal> for PrimVal {
fn from_iter<T: IntoIterator<Item = PrimVal>>(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<Self> {
Ok(PrimVal::from_string(s.to_string()))
}
}
impl_try_from!(PrimVal {
for String => |x| x.as_string();
for f64 => |x| x.as_f64();
for bool => |x| x.as_bool();
for Vec<String> => |x| x.as_vec();
});
impl From<bool> for PrimVal {
fn from(x: bool) -> Self {
PrimVal(x.to_string())
}
}
impl From<i32> for PrimVal {
fn from(s: i32) -> Self {
PrimVal(s.to_string())
}
}
impl From<u32> for PrimVal {
fn from(s: u32) -> Self {
PrimVal(s.to_string())
}
}
impl From<f32> for PrimVal {
fn from(s: f32) -> Self {
PrimVal(s.to_string())
}
}
impl From<u8> for PrimVal {
fn from(s: u8) -> Self {
PrimVal(s.to_string())
}
}
impl From<f64> for PrimVal {
fn from(s: f64) -> Self {
PrimVal(s.to_string())
}
}
impl From<&str> for PrimVal {
fn from(s: &str) -> Self {
PrimVal(s.to_string())
}
}
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(|| "<invalid json value>".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<String> {
Ok(self.0.to_owned())
}
pub fn as_f64(&self) -> Result<f64> {
self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to f64: {}", &self, e))
}
pub fn as_i32(&self) -> Result<i32> {
self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to i32: {}", &self, e))
}
pub fn as_bool(&self) -> Result<bool> {
self.0.parse().map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e))
}
pub fn as_vec(&self) -> Result<Vec<String>> {
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::Value> {
serde_json::from_str::<serde_json::Value>(&self.0)
.with_context(|| format!("Couldn't convert {:#?} to a json object", &self))
}
}
fn parse_vec(a: String) -> Result<Vec<String>> {
match a.strip_prefix('[').and_then(|x| x.strip_suffix(']')) {
Some(content) => {
let mut items: Vec<String> = 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] += &it;
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");
}
}

View file

@ -1,12 +1,12 @@
use crate::{ use crate::{
config::{element::WidgetDefinition, window_definition::WindowName}, config::{element::WidgetDefinition, window_definition::WindowName},
eww_state::*, eww_state::*,
value::AttrName,
}; };
use anyhow::*; use anyhow::*;
use gtk::prelude::*; use gtk::prelude::*;
use itertools::Itertools; use itertools::Itertools;
use std::collections::HashMap; use std::collections::HashMap;
use yuck::value::AttrName;
use std::process::Command; use std::process::Command;
use widget_definitions::*; use widget_definitions::*;
@ -132,7 +132,7 @@ macro_rules! resolve_block {
let attr_map: Result<_> = try { let attr_map: Result<_> = try {
::maplit::hashmap! { ::maplit::hashmap! {
$( $(
crate::value::AttrName(::std::stringify!($attr_name).to_owned()) => yuck::value::AttrName(::std::stringify!($attr_name).to_owned()) =>
resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?) resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?)
),* ),*
} }
@ -154,7 +154,7 @@ macro_rules! resolve_block {
}; };
(@get_value $args:ident, $name:expr, = $default:expr) => { (@get_value $args:ident, $name:expr, = $default:expr) => {
$args.widget.get_attr($name).cloned().unwrap_or(AttrVal::from_primitive($default)) $args.widget.get_attr($name).cloned().unwrap_or(simplexpr::SimplExpr::synth_literal($default))
}; };
(@get_value $args:ident, $name:expr,) => { (@get_value $args:ident, $name:expr,) => {

View file

@ -3,7 +3,6 @@ use super::{run_command, BuilderArgs};
use crate::{ use crate::{
config, enum_parse, eww_state, resolve_block, config, enum_parse, eww_state, resolve_block,
util::{list_difference, parse_duration}, util::{list_difference, parse_duration},
value::AttrVal,
widgets::widget_node, widgets::widget_node,
}; };
use anyhow::*; use anyhow::*;

View file

@ -5,7 +5,7 @@ use crate::{
WindowName, WindowName,
}, },
eww_state::EwwState, eww_state::EwwState,
value::{AttrName, AttrVal, VarName}, dynval::{AttrName, AttrVal, VarName},
}; };
use anyhow::*; use anyhow::*;
use dyn_clone; use dyn_clone;

View file

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
/// stores the left and right end of a span, and a given file identifier. /// stores the left and right end of a span, and a given file identifier.
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] #[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize, pub usize); pub struct Span(pub usize, pub usize, pub usize);
pub static DUMMY_SPAN: Span = Span(usize::MAX, usize::MAX, usize::MAX);
impl std::fmt::Display for Span { impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -73,6 +74,11 @@ impl SimplExpr {
Self::Literal(span, DynVal(s, Some(span))) 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 {
Self::Literal(DUMMY_SPAN, DynVal(s, Some(DUMMY_SPAN)))
}
pub fn span(&self) -> Span { pub fn span(&self) -> Span {
match self { match self {
SimplExpr::Literal(span, _) => *span, SimplExpr::Literal(span, _) => *span,

View file

@ -10,11 +10,11 @@ pub type Result<T> = std::result::Result<T, ConversionError>;
pub struct ConversionError { pub struct ConversionError {
pub value: DynVal, pub value: DynVal,
pub target_type: &'static str, pub target_type: &'static str,
pub source: Option<Box<dyn std::error::Error>>, pub source: Option<Box<dyn std::error::Error + Sync + Send + 'static>>,
} }
impl ConversionError { impl ConversionError {
fn new(value: DynVal, target_type: &'static str, source: impl std::error::Error + 'static) -> Self { fn new(value: DynVal, target_type: &'static str, source: impl std::error::Error + 'static + Sync + Send) -> Self {
ConversionError { value, target_type, source: Some(Box::new(source)) } ConversionError { value, target_type, source: Some(Box::new(source)) }
} }

View file

@ -14,13 +14,13 @@ pub enum Error {
ConversionError(#[from] dynval::ConversionError), ConversionError(#[from] dynval::ConversionError),
#[error("{1}")] #[error("{1}")]
Spanned(Span, Box<dyn std::error::Error>), Spanned(Span, Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)] #[error(transparent)]
Eval(#[from] crate::eval::EvalError), Eval(#[from] crate::eval::EvalError),
#[error(transparent)] #[error(transparent)]
Other(#[from] Box<dyn std::error::Error>), Other(#[from] Box<dyn std::error::Error + Send + Sync>),
} }
impl Error { impl Error {

View file

@ -23,6 +23,7 @@ pretty_assertions = "0.7"
strum = { version = "0.21", features = ["derive"] } strum = { version = "0.21", features = ["derive"] }
anyhow = "1" anyhow = "1"
static_assertions = "1.1"
simplexpr = { path = "../simplexpr" } simplexpr = { path = "../simplexpr" }

View file

@ -1,4 +1,4 @@
use eww_config::{ use yuck::{
config::{widget_definition::WidgetDefinition, widget_use::WidgetUse, *}, config::{widget_definition::WidgetDefinition, widget_use::WidgetUse, *},
error::AstError, error::AstError,
format_diagnostic::ToDiagnostic, format_diagnostic::ToDiagnostic,
@ -19,8 +19,8 @@ fn main() {
let file_id_use = files.add("use.eww", input_use); let file_id_use = files.add("use.eww", input_use);
let file_id_def = files.add("def.eww", input_def); let file_id_def = files.add("def.eww", input_def);
let parsed_use = WidgetUse::from_ast(eww_config::parser::parse_string(file_id_use, input_use).unwrap()).unwrap(); let parsed_use = WidgetUse::from_ast(yuck::parser::parse_string(file_id_use, input_use).unwrap()).unwrap();
let parsed_def = WidgetDefinition::from_ast(eww_config::parser::parse_string(file_id_def, input_def).unwrap()).unwrap(); let parsed_def = WidgetDefinition::from_ast(yuck::parser::parse_string(file_id_def, input_def).unwrap()).unwrap();
let defs = maplit::hashmap! { let defs = maplit::hashmap! {
"foo".to_string() => parsed_def, "foo".to_string() => parsed_def,
}; };

View file

@ -27,7 +27,7 @@ pub enum AttrError {
EvaluationError(Span, EvalError), EvaluationError(Span, EvalError),
#[error("{1}")] #[error("{1}")]
Other(Span, Box<dyn std::error::Error>), Other(Span, Box<dyn std::error::Error + Sync + Send + 'static>),
} }
impl AttrError { impl AttrError {
@ -86,7 +86,7 @@ impl Attributes {
pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError> pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError>
where where
E: std::error::Error + 'static, E: std::error::Error + 'static + Sync + Send,
T: FromDynVal<Err = E>, T: FromDynVal<Err = E>,
{ {
let ast: SimplExpr = self.ast_required(&key)?; let ast: SimplExpr = self.ast_required(&key)?;
@ -99,7 +99,7 @@ impl Attributes {
pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, AstError> pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, AstError>
where where
E: std::error::Error + 'static, E: std::error::Error + 'static + Sync + Send,
T: FromDynVal<Err = E>, T: FromDynVal<Err = E>,
{ {
let ast: SimplExpr = match self.ast_optional(key)? { let ast: SimplExpr = match self.ast_optional(key)? {

View file

@ -46,10 +46,10 @@ impl FromAst for TopLevel {
#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)]
pub struct Config { pub struct Config {
widget_definitions: HashMap<String, WidgetDefinition>, pub widget_definitions: HashMap<String, WidgetDefinition>,
window_definitions: HashMap<String, WindowDefinition>, pub window_definitions: HashMap<String, WindowDefinition>,
var_definitions: HashMap<VarName, VarDefinition>, pub var_definitions: HashMap<VarName, VarDefinition>,
script_vars: HashMap<VarName, ScriptVarDefinition>, pub script_vars: HashMap<VarName, ScriptVarDefinition>,
} }
impl FromAst for Config { impl FromAst for Config {

View file

@ -1,6 +1,6 @@
pub mod attributes; pub mod attributes;
pub mod backend_window_options; pub mod backend_window_options;
mod config; pub mod config;
pub mod config_parse_error; pub mod config_parse_error;
pub mod script_var_definition; pub mod script_var_definition;
#[cfg(test)] #[cfg(test)]
@ -11,3 +11,5 @@ pub mod widget_definition;
pub mod widget_use; pub mod widget_use;
pub mod window_definition; pub mod window_definition;
pub mod window_geometry; pub mod window_geometry;
pub use config::*;

View file

@ -32,7 +32,7 @@ pub enum VarSource {
// TODO allow for other executors? (python, etc) // TODO allow for other executors? (python, etc)
Shell(String), Shell(String),
#[serde(skip)] #[serde(skip)]
Function(fn() -> Result<DynVal, Box<dyn std::error::Error>>), Function(fn() -> Result<DynVal, Box<dyn std::error::Error + Sync>>),
} }
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct PollScriptVar { pub struct PollScriptVar {

View file

@ -28,7 +28,7 @@ pub enum AstError {
ConversionError(#[from] dynval::ConversionError), ConversionError(#[from] dynval::ConversionError),
#[error("{1}")] #[error("{1}")]
Other(Option<Span>, Box<dyn std::error::Error>), Other(Option<Span>, Box<dyn std::error::Error + Sync + Send + 'static>),
#[error(transparent)] #[error(transparent)]
AttrError(#[from] AttrError), AttrError(#[from] AttrError),
@ -40,6 +40,10 @@ pub enum AstError {
ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError> }, ParseError { file_id: Option<usize>, source: lalrpop_util::ParseError<usize, lexer::Token, parse_error::ParseError> },
} }
// static_assertions::assert_impl_all!(AstError: Send, Sync);
// static_assertions::assert_impl_all!(dynval::ConversionError: Send, Sync);
// static_assertions::assert_impl_all!(lalrpop_util::ParseError < usize, lexer::Token, parse_error::ParseError>: Send, Sync);
impl AstError { impl AstError {
pub fn get_span(&self) -> Option<Span> { pub fn get_span(&self) -> Option<Span> {
match self { match self {