diff --git a/Cargo.lock b/Cargo.lock index a5c995c..922facd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" + [[package]] name = "ascii-canvas" version = "3.0.0" @@ -195,6 +201,7 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" name = "eww_config" version = "0.1.0" dependencies = [ + "anyhow", "codespan-reporting", "derive_more", "insta", diff --git a/Cargo.toml b/Cargo.toml index 73a9e5e..9c02841 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ serde_json = "1.0" lazy_static = "1.4" pretty_assertions = "0.7" +anyhow = "1" + simplexpr = { path = "../../projects/simplexpr" } diff --git a/src/config/config.rs b/src/config/config.rs index 6c3f4f4..9c77961 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -2,12 +2,16 @@ use std::collections::HashMap; use simplexpr::SimplExpr; -use super::{var::VarDefinition, widget_definition::WidgetDefinition, widget_use::WidgetUse}; +use super::{ + script_var_definition::ScriptVarDefinition, var_definition::VarDefinition, widget_definition::WidgetDefinition, + widget_use::WidgetUse, +}; use crate::{ + config::script_var_definition::{PollScriptVar, TailScriptVar}, error::{AstError, AstResult, OptionAstErrorExt}, parser::{ ast::{Ast, AstIterator, Span}, - element::{Element, FromAst}, + element::{Element, FromAst, FromAstElementContent}, }, spanned, value::{AttrName, VarName}, @@ -15,6 +19,7 @@ use crate::{ pub enum TopLevel { VarDefinition(VarDefinition), + ScriptVarDefinition(ScriptVarDefinition), WidgetDefinition(WidgetDefinition), } @@ -22,11 +27,21 @@ impl FromAst for TopLevel { fn from_ast(e: Ast) -> AstResult { let span = e.span(); spanned!(e.span(), { - let list = e.as_list_ref()?; - match list.first().or_missing()?.as_symbol_ref()?.as_ref() { - "defwidget" => Self::WidgetDefinition(WidgetDefinition::from_ast(e)?), - "defvar" => Self::VarDefinition(VarDefinition::from_ast(e)?), - x => return Err(AstError::UnknownToplevel(Some(span), x.to_string())), + let list = e.as_list()?; + let mut iter = AstIterator::new(list.into_iter()); + let (sym_span, element_name) = iter.expect_symbol()?; + match element_name.as_str() { + x if x == WidgetDefinition::get_element_name() => { + Self::WidgetDefinition(WidgetDefinition::from_tail(span, iter)?) + } + x if x == VarDefinition::get_element_name() => Self::VarDefinition(VarDefinition::from_tail(span, iter)?), + x if x == PollScriptVar::get_element_name() => { + Self::ScriptVarDefinition(ScriptVarDefinition::Poll(PollScriptVar::from_tail(span, iter)?)) + } + x if x == TailScriptVar::get_element_name() => { + Self::ScriptVarDefinition(ScriptVarDefinition::Tail(TailScriptVar::from_tail(span, iter)?)) + } + x => return Err(AstError::UnknownToplevel(Some(sym_span), x.to_string())), } }) } @@ -36,17 +51,22 @@ impl FromAst for TopLevel { pub struct Config { widget_definitions: HashMap, var_definitions: HashMap, + script_vars: HashMap, } impl FromAst for Config { fn from_ast(e: Ast) -> AstResult { let list = e.as_list()?; - let mut config = Self { widget_definitions: HashMap::new(), var_definitions: HashMap::new() }; + let mut config = + Self { widget_definitions: HashMap::new(), var_definitions: HashMap::new(), script_vars: HashMap::new() }; for element in list { match TopLevel::from_ast(element)? { TopLevel::VarDefinition(x) => { config.var_definitions.insert(x.name.clone(), x); } + TopLevel::ScriptVarDefinition(x) => { + config.script_vars.insert(x.name().clone(), x); + } TopLevel::WidgetDefinition(x) => { config.widget_definitions.insert(x.name.clone(), x); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 683abb4..c1c9b70 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ mod config; +pub mod script_var_definition; pub mod validate; -pub mod var; +pub mod var_definition; pub mod widget_definition; pub mod widget_use; diff --git a/src/config/script_var_definition.rs b/src/config/script_var_definition.rs new file mode 100644 index 0000000..ca46c80 --- /dev/null +++ b/src/config/script_var_definition.rs @@ -0,0 +1,73 @@ +use std::collections::HashMap; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{ + error::{AstError, AstResult}, + parser::{ + ast::{Ast, AstIterator, Span}, + element::{Element, FromAst, FromAstElementContent}, + }, + spanned, + value::{AttrName, VarName}, +}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ScriptVarDefinition { + Poll(PollScriptVar), + Tail(TailScriptVar), +} + +impl ScriptVarDefinition { + pub fn name(&self) -> &VarName { + match self { + ScriptVarDefinition::Poll(x) => &x.name, + ScriptVarDefinition::Tail(x) => &x.name, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum VarSource { + // TODO allow for other executors? (python, etc) + Shell(String), + Function(fn() -> Result>), +} +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PollScriptVar { + pub name: VarName, + pub command: VarSource, + pub interval: std::time::Duration, +} + +impl FromAstElementContent for PollScriptVar { + fn get_element_name() -> &'static str { + "defpollvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let attrs: HashMap = iter.expect_key_values()?; + let interval = attrs.get("interval").unwrap(); + let interval = crate::util::parse_duration(interval).map_err(|e| AstError::Other(Some(span), e.into()))?; + let (_, script) = iter.expect_value()?; + Ok(Self { name: VarName(name), command: VarSource::Shell(script.to_string()), interval }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TailScriptVar { + pub name: VarName, + pub command: String, +} +impl FromAstElementContent for TailScriptVar { + fn get_element_name() -> &'static str { + "deftailvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (_, script) = iter.expect_value()?; + Ok(Self { name: VarName(name), command: script.to_string() }) + } +} diff --git a/src/config/var.rs b/src/config/var.rs deleted file mode 100644 index a949c94..0000000 --- a/src/config/var.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::collections::HashMap; - -use simplexpr::{dynval::DynVal, SimplExpr}; - -use crate::{ - error::AstResult, - parser::{ - ast::{Ast, AstIterator, Span}, - element::{Element, FromAst}, - }, - spanned, - value::{AttrName, VarName}, -}; - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct VarDefinition { - pub name: VarName, - pub initial_value: DynVal, - pub span: Span, -} - -impl FromAst for VarDefinition { - fn from_ast(e: Ast) -> AstResult { - let span = e.span(); - spanned!(e.span(), { - let list = e.as_list()?; - let mut iter = AstIterator::new(list.into_iter()); - let _ = iter.expect_symbol()?; - let (_, name) = iter.expect_symbol()?; - let (_, initial_value) = iter.expect_value()?; - Self { name: VarName(name), initial_value, span } - }) - } -} diff --git a/src/config/var_definition.rs b/src/config/var_definition.rs new file mode 100644 index 0000000..b60d078 --- /dev/null +++ b/src/config/var_definition.rs @@ -0,0 +1,32 @@ +use std::collections::HashMap; + +use simplexpr::{dynval::DynVal, SimplExpr}; + +use crate::{ + error::AstResult, + parser::{ + ast::{Ast, AstIterator, Span}, + element::{Element, FromAst, FromAstElementContent}, + }, + spanned, + value::{AttrName, VarName}, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct VarDefinition { + pub name: VarName, + pub initial_value: DynVal, + pub span: Span, +} + +impl FromAstElementContent for VarDefinition { + fn get_element_name() -> &'static str { + "defvar" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (_, initial_value) = iter.expect_value()?; + Ok(Self { name: VarName(name), initial_value, span }) + } +} diff --git a/src/config/widget_definition.rs b/src/config/widget_definition.rs index a2ffd9a..172b687 100644 --- a/src/config/widget_definition.rs +++ b/src/config/widget_definition.rs @@ -6,7 +6,7 @@ use crate::{ error::AstResult, parser::{ ast::{Ast, AstIterator, Span}, - element::{Element, FromAst}, + element::{Element, FromAst, FromAstElementContent}, }, spanned, value::{AttrName, VarName}, @@ -22,23 +22,18 @@ pub struct WidgetDefinition { pub args_span: Span, } -impl FromAst for WidgetDefinition { - fn from_ast(e: Ast) -> AstResult { - let span = e.span(); - spanned!(e.span(), { - let list = e.as_list()?; - let mut iter = AstIterator::new(list.into_iter()); +impl FromAstElementContent for WidgetDefinition { + fn get_element_name() -> &'static str { + "defwidget" + } - let (_, def_type) = iter.expect_symbol()?; - assert!(def_type == "defwidget"); - - let (_, name) = iter.expect_symbol()?; - let (args_span, expected_args) = iter.expect_array()?; - let expected_args = expected_args.into_iter().map(|x| x.as_symbol().map(AttrName)).collect::>()?; - let widget = iter.expect_any().and_then(WidgetUse::from_ast)?; - // TODO verify that this was the last element in the list - // iter.expect_done()?; - Self { name, expected_args, widget, span, args_span } - }) + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (_, name) = iter.expect_symbol()?; + let (args_span, expected_args) = iter.expect_array()?; + let expected_args = expected_args.into_iter().map(|x| x.as_symbol().map(AttrName)).collect::>()?; + let widget = iter.expect_any().and_then(WidgetUse::from_ast)?; + // TODO verify that this was the last element in the list + // iter.expect_done()?; + Ok(Self { name, expected_args, widget, span, args_span }) } } diff --git a/src/error.rs b/src/error.rs index 9b7b874..8b6fcf2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,6 +20,10 @@ pub enum AstError { WrongExprType(Option, AstType, AstType), #[error("Expected to get a value, but got {1}")] NotAValue(Option, AstType), + #[error("Expected element {1}, but read {2}")] + MismatchedElementName(Option, String, String), + #[error("{1}")] + Other(Option, Box), #[error(transparent)] ValidationError(#[from] ValidationError), @@ -35,6 +39,8 @@ impl AstError { AstError::MissingNode(span) => *span, AstError::WrongExprType(span, ..) => *span, AstError::NotAValue(span, ..) => *span, + AstError::MismatchedElementName(span, ..) => *span, + AstError::Other(span, ..) => *span, AstError::ValidationError(error) => None, // TODO none here is stupid AstError::ParseError { file_id, source } => file_id.and_then(|id| get_parse_error_span(id, source)), } @@ -67,9 +73,14 @@ fn get_parse_error_span( pub fn spanned(span: Span, err: impl Into) -> AstError { use AstError::*; match err.into() { - AstError::UnknownToplevel(None, x) => AstError::UnknownToplevel(Some(span), x), - AstError::MissingNode(None) => AstError::MissingNode(Some(span)), - AstError::WrongExprType(None, x, y) => AstError::WrongExprType(Some(span), x, y), + UnknownToplevel(None, x) => UnknownToplevel(Some(span), x), + MissingNode(None) => MissingNode(Some(span)), + WrongExprType(None, x, y) => WrongExprType(Some(span), x, y), + UnknownToplevel(None, x) => UnknownToplevel(Some(span), x), + MissingNode(None) => MissingNode(Some(span)), + NotAValue(None, x) => NotAValue(Some(span), x), + MismatchedElementName(None, x, y) => MismatchedElementName(Some(span), x, y), + Other(None, x) => Other(Some(span), x), x => x, } } diff --git a/src/lib.rs b/src/lib.rs index 687c8f1..91cace7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,4 +6,5 @@ pub mod config; pub mod error; pub mod format_diagnostic; pub mod parser; +mod util; pub mod value; diff --git a/src/parser/element.rs b/src/parser/element.rs index 7455607..9d1201b 100644 --- a/src/parser/element.rs +++ b/src/parser/element.rs @@ -1,7 +1,7 @@ use super::ast::{Ast, AstIterator, AstType, Span}; use crate::{error::*, parser, spanned, value::AttrName}; use itertools::Itertools; -use simplexpr::ast::SimplExpr; +use simplexpr::{ast::SimplExpr, dynval::DynVal}; use std::{ collections::{HashMap, LinkedList}, iter::FromIterator, @@ -18,6 +18,34 @@ impl FromAst for Ast { } } +impl FromAst for String { + fn from_ast(e: Ast) -> AstResult { + Ok(e.as_value()?.to_string()) + } +} + +/// A trait that allows creating a type from the tail of a list-node. +/// I.e. to parse (foo [a b] (c d)), [from_tail] would just get [a b] (c d). +pub trait FromAstElementContent: Sized { + fn get_element_name() -> &'static str; + fn from_tail>(span: Span, iter: AstIterator) -> AstResult; +} + +impl FromAst for T { + fn from_ast(e: Ast) -> AstResult { + let span = e.span(); + spanned!(e.span(), { + let list = e.as_list()?; + let mut iter = AstIterator::new(list.into_iter()); + let (_, element_name) = iter.expect_symbol()?; + if Self::get_element_name() != element_name { + return Err(AstError::MismatchedElementName(Some(span), Self::get_element_name().to_string(), element_name)); + } + Self::from_tail(span, iter)? + }) + } +} + impl FromAst for SimplExpr { fn from_ast(e: Ast) -> AstResult { match e { diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..a15474f --- /dev/null +++ b/src/util.rs @@ -0,0 +1,14 @@ +pub fn parse_duration(s: &str) -> anyhow::Result { + use std::time::Duration; + if s.ends_with("ms") { + Ok(Duration::from_millis(s.trim_end_matches("ms").parse()?)) + } else if s.ends_with('s') { + Ok(Duration::from_secs(s.trim_end_matches('s').parse()?)) + } else if s.ends_with('m') { + Ok(Duration::from_secs(s.trim_end_matches('m').parse::()? * 60)) + } else if s.ends_with('h') { + Ok(Duration::from_secs(s.trim_end_matches('h').parse::()? * 60 * 60)) + } else { + Err(anyhow::anyhow!("Failed to parse duration `{}`", s)) + } +}