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},
display_backend, eww_state,
script_var_handler::*,
value::{Coords, NumWithUnit, PrimVal, VarName},
dynval::{Coords, NumWithUnit, DynVal, VarName},
EwwPaths,
};
use anyhow::*;
@ -38,7 +38,7 @@ pub type DaemonResponseReceiver = tokio::sync::mpsc::UnboundedReceiver<DaemonRes
#[derive(Debug)]
pub enum DaemonCommand {
NoOp,
UpdateVars(Vec<(VarName, PrimVal)>),
UpdateVars(Vec<(VarName, DynVal)>),
ReloadConfigAndCss(DaemonResponseSender),
UpdateConfig(config::EwwConfig),
UpdateCss(String),
@ -199,7 +199,7 @@ impl App {
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)
}

View file

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

View file

@ -1,50 +1,56 @@
use anyhow::*;
use std::collections::HashMap;
use crate::{
util,
value::{PrimVal, VarName},
use yuck::{
config::{
script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition, window_definition::WindowDefinition,
},
parser::from_ast::FromAst,
value::VarName,
};
use super::{
element::WidgetDefinition,
xml_ext::{XmlElement, XmlNode},
EwwWindowDefinition, RawEwwWindowDefinition, ScriptVar, WindowName,
};
use simplexpr::dynval::DynVal;
use std::path::PathBuf;
/// Eww configuration structure.
#[derive(Debug, Clone)]
pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, EwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimVal>,
script_vars: HashMap<VarName, ScriptVar>,
windows: HashMap<String, WindowDefinition>,
initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVarDefinition>,
pub filepath: PathBuf,
}
impl EwwConfig {
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> {
let RawEwwConfig { windows, initial_variables, script_vars, filepath, widgets } = conf;
pub fn generate(config: yuck::config::Config) -> Result<Self> {
Ok(EwwConfig {
windows: windows
windows: config
.window_definitions
.into_iter()
.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<_, _>>>()?,
widgets,
initial_variables,
script_vars,
filepath,
widgets: config.widget_definitions,
initial_variables: config.var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(),
script_vars: config.script_vars,
filepath: todo!(),
})
}
// 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 =
self.script_vars.iter().map(|var| Ok((var.0.clone(), var.1.initial_value()?))).collect::<Result<HashMap<_, _>>>()?;
vars.extend(self.initial_variables.clone());
@ -68,222 +74,40 @@ impl EwwConfig {
}
}
/// Raw Eww configuration, before expanding widget usages.
#[derive(Debug, Clone)]
pub struct RawEwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<WindowName, RawEwwWindowDefinition>,
initial_variables: HashMap<VarName, PrimVal>,
script_vars: HashMap<VarName, ScriptVar>,
pub filepath: PathBuf,
}
// Raw Eww configuration, before expanding widget usages.
//#[derive(Debug, Clone)]
// pub struct RawEwwConfig {
// widgets: HashMap<String, WidgetDefinition>,
// windows: HashMap<WindowName, RawEwwWindowDefinition>,
// initial_variables: HashMap<VarName, DynVal>,
// script_vars: HashMap<VarName, ScriptVar>,
// pub filepath: PathBuf,
//}
impl RawEwwConfig {
pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec<RawEwwConfig>) -> Result<RawEwwConfig> {
let config_path = eww_config.filepath.clone();
let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
log::error!(
"{} '{}' defined twice (defined in {} and in {})",
what,
conflict,
config_path.display(),
included_path.display()
);
};
for included_config in includes {
for conflict in util::extend_safe(&mut eww_config.widgets, included_config.widgets) {
log_conflict("widget", &conflict, &included_config.filepath)
}
for conflict in util::extend_safe(&mut eww_config.windows, included_config.windows) {
log_conflict("window", &conflict.to_string(), &included_config.filepath)
}
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)
}
for conflict in util::extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) {
log_conflict("var", &conflict.to_string(), &included_config.filepath)
}
}
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);
}
}
// impl RawEwwConfig {
// pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec<RawEwwConfig>) -> Result<RawEwwConfig> {
// let config_path = eww_config.filepath.clone();
// let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
// log::error!(
//"{} '{}' defined twice (defined in {} and in {})",
// what,
// conflict,
// config_path.display(),
// included_path.display()
//);
//};
// for included_config in includes {
// for conflict in util::extend_safe(&mut eww_config.widgets, included_config.widgets) {
// log_conflict("widget", &conflict, &included_config.filepath)
//}
// for conflict in util::extend_safe(&mut eww_config.windows, included_config.windows) {
// log_conflict("window", &conflict.to_string(), &included_config.filepath)
//}
// 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)
//}
// for conflict in util::extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) {
// log_conflict("var", &conflict.to_string(), &included_config.filepath)
//}
// Ok(eww_config)
//}

View file

@ -1,6 +1,6 @@
use crate::{
config::{system_stats::*, PollScriptVar, ScriptVar, VarSource},
value::{PrimVal as PrimitiveValue, VarName},
dynval::{DynVal as PrimitiveValue, VarName},
};
use std::{collections::HashMap, time::Duration};
@ -20,16 +20,16 @@ macro_rules! builtin_vars {
pub fn get_inbuilt_vars() -> HashMap<VarName, ScriptVar> {
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(PrimitiveValue::from(cores())),
"EWW_TEMPS" => || Ok(Primitivedynval::from(cores())),
// @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["/"]}}`
"EWW_DISK" => || Ok(PrimitiveValue::from(disk())),
"EWW_DISK" => || Ok(Primitivedynval::from(disk())),
// @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() {
Err(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)
"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
"EWW_NET" => || Ok(PrimitiveValue::from(net())),
"EWW_NET" => || Ok(Primitivedynval::from(net())),
}
}

View file

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

View file

@ -9,7 +9,7 @@ use super::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VarSource {
Shell(String),
Function(fn() -> Result<PrimVal>),
Function(fn() -> Result<DynVal>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PollScriptVar {
@ -19,7 +19,7 @@ pub struct PollScriptVar {
}
impl PollScriptVar {
pub fn run_once(&self) -> Result<PrimVal> {
pub fn run_once(&self) -> Result<DynVal> {
match &self.command {
VarSource::Shell(x) => run_command(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 {
ScriptVar::Poll(x) => match &x.command {
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()))
}
},
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
fn run_command(cmd: &str) -> Result<PrimVal> {
fn run_command(cmd: &str) -> Result<DynVal> {
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');
Ok(PrimVal::from(output))
Ok(DynVal::from(output))
}

View file

@ -1,9 +1,14 @@
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 serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use std::{collections::HashMap, str::FromStr};
use yuck::config::{
backend_window_options::StrutDefinition,
window_definition::{WindowDefinition, WindowStacking},
window_geometry::WindowGeometry,
};
#[derive(Debug, Clone, PartialEq)]
pub enum EwwWindowType {
@ -36,31 +41,31 @@ impl Default for EwwWindowType {
/// **Use this** rather than `[RawEwwWindowDefinition]`.
#[derive(Debug, Clone)]
pub struct EwwWindowDefinition {
pub name: WindowName,
pub geometry: EwwWindowGeometry,
pub name: String,
pub geometry: WindowGeometry,
pub stacking: WindowStacking,
pub screen_number: Option<i32>,
pub monitor_number: Option<i32>,
pub widget: Box<dyn widget_node::WidgetNode>,
pub focusable: bool,
#[cfg(feature = "x11")]
pub window_type: EwwWindowType,
#[cfg(feature = "x11")]
pub struts: StrutDefinition,
#[cfg(feature = "wayland")]
pub exclusive: bool,
}
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 {
name: window.name,
geometry: window.geometry,
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)?,
focusable: window.focusable,
#[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 serde::{Deserialize, Serialize};
use smart_default::SmartDefault;

View file

@ -1,16 +1,16 @@
use crate::{
config::window_definition::WindowName,
value::{AttrName, AttrValElement, VarName},
dynval::{AttrName, AttrValElement, VarName},
};
use anyhow::*;
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
/// a gtk widget. These are created and initialized in EwwState::resolve.
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>,
}
@ -21,7 +21,7 @@ impl StateChangeHandler {
/// Run the StateChangeHandler.
/// [`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
.unresolved_values
.clone()
@ -61,7 +61,7 @@ impl EwwWindowState {
#[derive(Default)]
pub struct EwwState {
windows: HashMap<WindowName, EwwWindowState>,
variables_state: HashMap<VarName, PrimVal>,
variables_state: HashMap<VarName, DynVal>,
}
impl std::fmt::Debug for EwwState {
@ -71,11 +71,11 @@ impl std::fmt::Debug for 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() }
}
pub fn get_variables(&self) -> &HashMap<VarName, PrimVal> {
pub fn get_variables(&self) -> &HashMap<VarName, DynVal> {
&self.variables_state
}
@ -91,7 +91,7 @@ impl EwwState {
/// Update the value of a variable, running all registered
/// [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);
// 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.
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))
}
/// 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
.iter()
.map(|element| match element {
@ -120,7 +120,7 @@ impl EwwState {
/// Resolve takes a function that applies a set of fully resolved attribute
/// 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,
window_name: &WindowName,
required_attributes: HashMap<AttrName, AttrVal>,

View file

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

View file

@ -5,7 +5,7 @@ use structopt::StructOpt;
use crate::{
app,
config::{AnchorPoint, WindowName},
value::{Coords, PrimVal, VarName},
dynval::{Coords, DynVal, VarName},
};
/// Struct that gets generated from `RawOpt`.
@ -61,7 +61,7 @@ pub enum ActionWithServer {
Update {
/// variable_name="new_value"-pairs that will be updated
#[structopt(parse(try_from_str = parse_var_update_arg))]
mappings: Vec<(VarName, PrimVal)>,
mappings: Vec<(VarName, DynVal)>,
},
/// 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
.split_once('=')
.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 {

View file

@ -2,7 +2,7 @@ use std::collections::HashMap;
use crate::{
app, config,
value::{PrimVal, VarName},
dynval::{DynVal, VarName},
};
use anyhow::*;
use app::DaemonCommand;
@ -197,7 +197,7 @@ impl TailVarHandler {
_ = handle.wait() => break,
_ = cancellation_token.cancelled() => break,
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)]))?;
}
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::{
config::{element::WidgetDefinition, window_definition::WindowName},
eww_state::*,
value::AttrName,
};
use anyhow::*;
use gtk::prelude::*;
use itertools::Itertools;
use std::collections::HashMap;
use yuck::value::AttrName;
use std::process::Command;
use widget_definitions::*;
@ -132,7 +132,7 @@ macro_rules! resolve_block {
let attr_map: Result<_> = try {
::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)?)
),*
}
@ -154,7 +154,7 @@ macro_rules! resolve_block {
};
(@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,) => {

View file

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

View file

@ -5,7 +5,7 @@ use crate::{
WindowName,
},
eww_state::EwwState,
value::{AttrName, AttrVal, VarName},
dynval::{AttrName, AttrVal, VarName},
};
use anyhow::*;
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.
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -73,6 +74,11 @@ 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 {
Self::Literal(DUMMY_SPAN, DynVal(s, Some(DUMMY_SPAN)))
}
pub fn span(&self) -> Span {
match self {
SimplExpr::Literal(span, _) => *span,

View file

@ -10,11 +10,11 @@ pub type Result<T> = std::result::Result<T, ConversionError>;
pub struct ConversionError {
pub value: DynVal,
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 {
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)) }
}

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ pub enum AttrError {
EvaluationError(Span, EvalError),
#[error("{1}")]
Other(Span, Box<dyn std::error::Error>),
Other(Span, Box<dyn std::error::Error + Sync + Send + 'static>),
}
impl AttrError {
@ -86,7 +86,7 @@ impl Attributes {
pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError>
where
E: std::error::Error + 'static,
E: std::error::Error + 'static + Sync + Send,
T: FromDynVal<Err = E>,
{
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>
where
E: std::error::Error + 'static,
E: std::error::Error + 'static + Sync + Send,
T: FromDynVal<Err = E>,
{
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)]
pub struct Config {
widget_definitions: HashMap<String, WidgetDefinition>,
window_definitions: HashMap<String, WindowDefinition>,
var_definitions: HashMap<VarName, VarDefinition>,
script_vars: HashMap<VarName, ScriptVarDefinition>,
pub widget_definitions: HashMap<String, WidgetDefinition>,
pub window_definitions: HashMap<String, WindowDefinition>,
pub var_definitions: HashMap<VarName, VarDefinition>,
pub script_vars: HashMap<VarName, ScriptVarDefinition>,
}
impl FromAst for Config {

View file

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

View file

@ -32,7 +32,7 @@ pub enum VarSource {
// TODO allow for other executors? (python, etc)
Shell(String),
#[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)]
pub struct PollScriptVar {

View file

@ -28,7 +28,7 @@ pub enum AstError {
ConversionError(#[from] dynval::ConversionError),
#[error("{1}")]
Other(Option<Span>, Box<dyn std::error::Error>),
Other(Option<Span>, Box<dyn std::error::Error + Sync + Send + 'static>),
#[error(transparent)]
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> },
}
// 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 {
pub fn get_span(&self) -> Option<Span> {
match self {