diff --git a/crates/eww/src/config/script_var.rs b/crates/eww/src/config/script_var.rs index 39bddfe..95ef72f 100644 --- a/crates/eww/src/config/script_var.rs +++ b/crates/eww/src/config/script_var.rs @@ -1,8 +1,21 @@ use std::process::Command; use anyhow::*; +use eww_shared_util::{Span, VarName}; use simplexpr::dynval::DynVal; -use yuck::config::script_var_definition::{ScriptVarDefinition, VarSource}; +use yuck::{ + config::script_var_definition::{ScriptVarDefinition, VarSource}, + gen_diagnostic, +}; + +use crate::error::DiagError; + +pub fn create_script_var_failed_error(span: Span, var_name: &VarName) -> DiagError { + DiagError::new(gen_diagnostic! { + msg = format!("Failed to compute value for `{}`", var_name), + label = span => "Defined here", + }) +} pub fn initial_value(var: &ScriptVarDefinition) -> Result { match var { @@ -10,15 +23,20 @@ pub fn initial_value(var: &ScriptVarDefinition) -> Result { VarSource::Function(f) => { f().map_err(|err| anyhow!(err)).with_context(|| format!("Failed to compute initial value for {}", &var.name())) } - VarSource::Shell(f) => run_command(f).with_context(|| format!("Failed to compute initial value for {}", &var.name())), + VarSource::Shell(span, f) => run_command(f).map_err(|_| anyhow!(create_script_var_failed_error(*span, var.name()))), }, - ScriptVarDefinition::Tail(_) => Ok(DynVal::from_string(String::new())), + ScriptVarDefinition::Listen(_) => Ok(DynVal::from_string(String::new())), } } + /// Run a command and get the output pub fn run_command(cmd: &str) -> Result { log::debug!("Running command: {}", cmd); - let output = String::from_utf8(Command::new("/bin/sh").arg("-c").arg(cmd).output()?.stdout)?; + let command = Command::new("/bin/sh").arg("-c").arg(cmd).output()?; + if !command.status.success() { + bail!("Execution of `{}` failed", cmd); + } + let output = String::from_utf8(command.stdout)?; let output = output.trim_matches('\n'); Ok(DynVal::from(output)) } diff --git a/crates/eww/src/error.rs b/crates/eww/src/error.rs new file mode 100644 index 0000000..b21348a --- /dev/null +++ b/crates/eww/src/error.rs @@ -0,0 +1,20 @@ +use codespan_reporting::diagnostic::Diagnostic; + +/// An error that contains a [Diagnostic] for ad-hoc creation of diagnostics. +#[derive(Debug)] +pub struct DiagError { + pub diag: Diagnostic, +} + +impl DiagError { + pub fn new(diag: Diagnostic) -> Self { + Self { diag } + } +} + +impl std::error::Error for DiagError {} +impl std::fmt::Display for DiagError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.diag.message) + } +} diff --git a/crates/eww/src/error_handling_ctx.rs b/crates/eww/src/error_handling_ctx.rs index 04ce248..04a43c8 100644 --- a/crates/eww/src/error_handling_ctx.rs +++ b/crates/eww/src/error_handling_ctx.rs @@ -9,6 +9,8 @@ use yuck::{ format_diagnostic::{eval_error_to_diagnostic, ToDiagnostic}, }; +use crate::error::DiagError; + lazy_static::lazy_static! { pub static ref ERROR_HANDLING_CTX: Arc> = Arc::new(Mutex::new(FsYuckFiles::new())); } @@ -18,29 +20,25 @@ pub fn clear_files() { } pub fn print_error(err: &anyhow::Error) { - match err.downcast_ref::() { - Some(err) => { - eprintln!("{:?}\n{}", err, stringify_diagnostic(err.to_diagnostic())); - } - None => match err.downcast_ref::() { - Some(err) => { - eprintln!("{:?}\n{}", err, stringify_diagnostic(eval_error_to_diagnostic(err, err.span().unwrap_or(DUMMY_SPAN)))); - } - None => { - log::error!("{:?}", err); - } - }, + if let Some(err) = err.downcast_ref::() { + eprintln!("{:?}\n{}", err, stringify_diagnostic(&err.diag)); + } else if let Some(err) = err.downcast_ref::() { + eprintln!("{:?}\n{}", err, stringify_diagnostic(&err.to_diagnostic())); + } else if let Some(err) = err.downcast_ref::() { + eprintln!("{:?}\n{}", err, stringify_diagnostic(&eval_error_to_diagnostic(err, err.span().unwrap_or(DUMMY_SPAN)))); + } else { + log::error!("{:?}", err); } } pub fn format_error(err: &anyhow::Error) -> String { match err.downcast_ref::() { - Some(err) => stringify_diagnostic(err.to_diagnostic()), + Some(err) => stringify_diagnostic(&err.to_diagnostic()), None => format!("{:?}", err), } } -pub fn stringify_diagnostic(diagnostic: Diagnostic) -> String { +pub fn stringify_diagnostic(diagnostic: &Diagnostic) -> String { use codespan_reporting::term; let config = term::Config::default(); let mut buf = Vec::new(); diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index 9cccbaf..f90edfb 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -32,6 +32,7 @@ pub mod script_var_handler; pub mod server; pub mod util; pub mod widgets; +pub mod error; fn main() { let opts: opts::Opt = opts::Opt::from_env(); diff --git a/crates/eww/src/script_var_handler.rs b/crates/eww/src/script_var_handler.rs index 0d94395..8ec7522 100644 --- a/crates/eww/src/script_var_handler.rs +++ b/crates/eww/src/script_var_handler.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::app; +use crate::{app, config::create_script_var_failed_error}; use anyhow::*; use app::DaemonCommand; @@ -11,7 +11,7 @@ use tokio::{ sync::mpsc::UnboundedSender, }; use tokio_util::sync::CancellationToken; -use yuck::config::script_var_definition::{PollScriptVar, ScriptVarDefinition, TailScriptVar}; +use yuck::config::script_var_definition::{ListenScriptVar, PollScriptVar, ScriptVarDefinition, VarSource}; /// Initialize the script var handler, and return a handle to that handler, which can be used to control /// the script var execution. @@ -23,7 +23,7 @@ pub fn init(evt_send: UnboundedSender) -> ScriptVarHandlerHandle rt.block_on(async { let _: Result<_> = try { let mut handler = ScriptVarHandler { - tail_handler: TailVarHandler::new(evt_send.clone())?, + listen_handler: ListenVarHandler::new(evt_send.clone())?, poll_handler: PollVarHandler::new(evt_send)?, }; crate::loop_select_exiting! { @@ -87,7 +87,7 @@ enum ScriptVarHandlerMsg { /// Handler that manages running and updating [ScriptVarDefinition]s struct ScriptVarHandler { - tail_handler: TailVarHandler, + listen_handler: ListenVarHandler, poll_handler: PollVarHandler, } @@ -95,14 +95,14 @@ impl ScriptVarHandler { async fn add(&mut self, script_var: ScriptVarDefinition) { match script_var { ScriptVarDefinition::Poll(var) => self.poll_handler.start(var).await, - ScriptVarDefinition::Tail(var) => self.tail_handler.start(var).await, + ScriptVarDefinition::Listen(var) => self.listen_handler.start(var).await, }; } /// Stop the handler that is responsible for a given variable. fn stop_for_variable(&mut self, name: &VarName) -> Result<()> { log::debug!("Stopping script var process for variable {}", name); - self.tail_handler.stop_for_variable(name); + self.listen_handler.stop_for_variable(name); self.poll_handler.stop_for_variable(name); Ok(()) } @@ -110,7 +110,7 @@ impl ScriptVarHandler { /// stop all running scripts and schedules fn stop_all(&mut self) { log::debug!("Stopping script-var-handlers"); - self.tail_handler.stop_all(); + self.listen_handler.stop_all(); self.poll_handler.stop_all(); } } @@ -163,8 +163,10 @@ impl PollVarHandler { fn run_poll_once(var: &PollScriptVar) -> Result { match &var.command { - yuck::config::script_var_definition::VarSource::Shell(x) => crate::config::script_var::run_command(x), - yuck::config::script_var_definition::VarSource::Function(x) => x().map_err(|e| anyhow!(e)), + VarSource::Shell(span, x) => crate::config::script_var::run_command(x).map_err(|_| { + anyhow!(create_script_var_failed_error(*span, &var.name)) + }), + VarSource::Function(x) => x().map_err(|e| anyhow!(e)), } } @@ -174,21 +176,21 @@ impl Drop for PollVarHandler { } } -struct TailVarHandler { +struct ListenVarHandler { evt_send: UnboundedSender, - tail_process_handles: HashMap, + listen_process_handles: HashMap, } -impl TailVarHandler { +impl ListenVarHandler { fn new(evt_send: UnboundedSender) -> Result { - let handler = TailVarHandler { evt_send, tail_process_handles: HashMap::new() }; + let handler = ListenVarHandler { evt_send, listen_process_handles: HashMap::new() }; Ok(handler) } - async fn start(&mut self, var: TailScriptVar) { + async fn start(&mut self, var: ListenScriptVar) { log::debug!("starting poll var {}", &var.name); let cancellation_token = CancellationToken::new(); - self.tail_process_handles.insert(var.name.clone(), cancellation_token.clone()); + self.listen_process_handles.insert(var.name.clone(), cancellation_token.clone()); let evt_send = self.evt_send.clone(); tokio::spawn(async move { @@ -215,18 +217,18 @@ impl TailVarHandler { } fn stop_for_variable(&mut self, name: &VarName) { - if let Some(token) = self.tail_process_handles.remove(name) { - log::debug!("stopped tail var {}", name); + if let Some(token) = self.listen_process_handles.remove(name) { + log::debug!("stopped listen-var {}", name); token.cancel(); } } fn stop_all(&mut self) { - self.tail_process_handles.drain().for_each(|(_, token)| token.cancel()); + self.listen_process_handles.drain().for_each(|(_, token)| token.cancel()); } } -impl Drop for TailVarHandler { +impl Drop for ListenVarHandler { fn drop(&mut self) { self.stop_all(); } diff --git a/crates/yuck/src/config/config.rs b/crates/yuck/src/config/config.rs index 0063d0a..b07021b 100644 --- a/crates/yuck/src/config/config.rs +++ b/crates/yuck/src/config/config.rs @@ -12,7 +12,7 @@ use super::{ window_definition::WindowDefinition, }; use crate::{ - config::script_var_definition::{PollScriptVar, TailScriptVar}, + config::script_var_definition::{ListenScriptVar, PollScriptVar}, error::{AstError, AstResult, OptionAstErrorExt}, parser::{ ast::Ast, @@ -59,8 +59,8 @@ impl FromAst for TopLevel { 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 if x == ListenScriptVar::get_element_name() => { + Self::ScriptVarDefinition(ScriptVarDefinition::Listen(ListenScriptVar::from_tail(span, iter)?)) } x if x == WindowDefinition::get_element_name() => Self::WindowDefinition(WindowDefinition::from_tail(span, iter)?), x => return Err(AstError::UnknownToplevel(sym_span, x.to_string())), diff --git a/crates/yuck/src/config/script_var_definition.rs b/crates/yuck/src/config/script_var_definition.rs index 8091110..862c323 100644 --- a/crates/yuck/src/config/script_var_definition.rs +++ b/crates/yuck/src/config/script_var_definition.rs @@ -15,14 +15,24 @@ use eww_shared_util::{AttrName, Span, VarName}; #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub enum ScriptVarDefinition { Poll(PollScriptVar), - Tail(TailScriptVar), + Listen(ListenScriptVar), } impl ScriptVarDefinition { pub fn name(&self) -> &VarName { match self { ScriptVarDefinition::Poll(x) => &x.name, - ScriptVarDefinition::Tail(x) => &x.name, + ScriptVarDefinition::Listen(x) => &x.name, + } + } + + pub fn command_span(&self) -> Option { + match self { + ScriptVarDefinition::Poll(x) => match x.command { + VarSource::Shell(span, _) => Some(span), + VarSource::Function(_) => None, + }, + ScriptVarDefinition::Listen(x) => Some(x.command_span), } } } @@ -30,10 +40,11 @@ impl ScriptVarDefinition { #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub enum VarSource { // TODO allow for other executors? (python, etc) - Shell(String), + Shell(Span, String), #[serde(skip)] Function(fn() -> Result>), } + #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] pub struct PollScriptVar { pub name: VarName, @@ -43,32 +54,32 @@ pub struct PollScriptVar { impl FromAstElementContent for PollScriptVar { fn get_element_name() -> &'static str { - "defpollvar" + "defpoll" } fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { let (_, name) = iter.expect_symbol()?; let mut attrs = iter.expect_key_values()?; let interval = attrs.primitive_required::("interval")?.as_duration()?; - // let interval = interval.as_duration()?; - let (_, script) = iter.expect_literal()?; - Ok(Self { name: VarName(name), command: VarSource::Shell(script.to_string()), interval }) + let (script_span, script) = iter.expect_literal()?; + Ok(Self { name: VarName(name), command: VarSource::Shell(script_span, script.to_string()), interval }) } } #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)] -pub struct TailScriptVar { +pub struct ListenScriptVar { pub name: VarName, pub command: String, + pub command_span: Span, } -impl FromAstElementContent for TailScriptVar { +impl FromAstElementContent for ListenScriptVar { fn get_element_name() -> &'static str { - "deftailvar" + "deflisten" } fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { let (_, name) = iter.expect_symbol()?; - let (_, script) = iter.expect_literal()?; - Ok(Self { name: VarName(name), command: script.to_string() }) + let (command_span, script) = iter.expect_literal()?; + Ok(Self { name: VarName(name), command: script.to_string(), command_span }) } } diff --git a/crates/yuck/src/format_diagnostic.rs b/crates/yuck/src/format_diagnostic.rs index d96586c3..e04f917 100644 --- a/crates/yuck/src/format_diagnostic.rs +++ b/crates/yuck/src/format_diagnostic.rs @@ -12,24 +12,25 @@ fn span_to_secondary_label(span: Span) -> Label { Label::secondary(span.2, span.0..span.1) } +#[macro_export] macro_rules! gen_diagnostic { ( $(msg = $msg:expr)? $(, label = $span:expr $(=> $label:expr)?)? $(, note = $note:expr)? $(,)? ) => { - Diagnostic::error() + ::codespan_reporting::diagnostic::Diagnostic::error() $(.with_message($msg.to_string()))? $(.with_labels(vec![ - Label::primary($span.2, $span.0..$span.1) + ::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1) $(.with_message($label))? ]))? $(.with_notes(vec![$note]))? }; ($msg:expr $(, $span:expr $(,)?)?) => {{ - Diagnostic::error() + ::codespan_reporting::diagnostic::Diagnostic::error() .with_message($msg.to_string()) - $(.with_labels(vec![Label::primary($span.2, $span.0..$span.1)]))? + $(.with_labels(vec![::codespan_reporting::diagnostic::Label::primary($span.2, $span.0..$span.1)]))? }}; }