Start migration
This commit is contained in:
parent
4f2e9cf063
commit
38f5307417
33 changed files with 149 additions and 3520 deletions
2043
Cargo.lock
generated
2043
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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("&", "&");
|
||||
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)
|
||||
//}
|
||||
|
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
util,
|
||||
value::{PrimVal, VarName},
|
||||
dynval::{DynVal, VarName},
|
||||
};
|
||||
|
||||
use anyhow::*;
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::value::Coords;
|
||||
use crate::dynval::Coords;
|
||||
use anyhow::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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)),
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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] += ⁢
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -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,) => {
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
WindowName,
|
||||
},
|
||||
eww_state::EwwState,
|
||||
value::{AttrName, AttrVal, VarName},
|
||||
dynval::{AttrName, AttrVal, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use dyn_clone;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)) }
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -23,6 +23,7 @@ pretty_assertions = "0.7"
|
|||
|
||||
strum = { version = "0.21", features = ["derive"] }
|
||||
anyhow = "1"
|
||||
static_assertions = "1.1"
|
||||
|
||||
|
||||
simplexpr = { path = "../simplexpr" }
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)? {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue