Add ad-hoc diagnostic errors

This commit is contained in:
elkowar 2021-07-23 15:04:03 +02:00
parent 9f70a22cf0
commit cff2f6beb8
No known key found for this signature in database
GPG Key ID: E321AD71B1D1F27F
8 changed files with 107 additions and 56 deletions

View File

@ -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<DynVal> {
match var {
@ -10,15 +23,20 @@ pub fn initial_value(var: &ScriptVarDefinition) -> Result<DynVal> {
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<DynVal> {
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))
}

20
crates/eww/src/error.rs Normal file
View File

@ -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<usize>,
}
impl DiagError {
pub fn new(diag: Diagnostic<usize>) -> 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)
}
}

View File

@ -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<Mutex<FsYuckFiles>> = 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::<AstError>() {
Some(err) => {
eprintln!("{:?}\n{}", err, stringify_diagnostic(err.to_diagnostic()));
}
None => match err.downcast_ref::<EvalError>() {
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::<DiagError>() {
eprintln!("{:?}\n{}", err, stringify_diagnostic(&err.diag));
} else if let Some(err) = err.downcast_ref::<AstError>() {
eprintln!("{:?}\n{}", err, stringify_diagnostic(&err.to_diagnostic()));
} else if let Some(err) = err.downcast_ref::<EvalError>() {
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::<AstError>() {
Some(err) => stringify_diagnostic(err.to_diagnostic()),
Some(err) => stringify_diagnostic(&err.to_diagnostic()),
None => format!("{:?}", err),
}
}
pub fn stringify_diagnostic(diagnostic: Diagnostic<usize>) -> String {
pub fn stringify_diagnostic(diagnostic: &Diagnostic<usize>) -> String {
use codespan_reporting::term;
let config = term::Config::default();
let mut buf = Vec::new();

View File

@ -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();

View File

@ -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<DaemonCommand>) -> 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<DynVal> {
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<DaemonCommand>,
tail_process_handles: HashMap<VarName, CancellationToken>,
listen_process_handles: HashMap<VarName, CancellationToken>,
}
impl TailVarHandler {
impl ListenVarHandler {
fn new(evt_send: UnboundedSender<DaemonCommand>) -> Result<Self> {
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();
}

View File

@ -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())),

View File

@ -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<Span> {
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<DynVal, Box<dyn std::error::Error + Sync + Send + 'static>>),
}
#[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<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
let (_, name) = iter.expect_symbol()?;
let mut attrs = iter.expect_key_values()?;
let interval = attrs.primitive_required::<DynVal, _>("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<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
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 })
}
}

View File

@ -12,24 +12,25 @@ fn span_to_secondary_label(span: Span) -> Label<usize> {
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)]))?
}};
}