From 618e652389ef80cbe1379c6bbaa604942e777834 Mon Sep 17 00:00:00 2001 From: elkowar <5300871+elkowar@users.noreply.github.com> Date: Thu, 22 Jul 2021 21:06:22 +0200 Subject: [PATCH] Implement YuckFiles for include handling --- Cargo.lock | 1 + crates/eww/Cargo.toml | 2 + crates/eww/src/app.rs | 19 +++++- crates/eww/src/config/eww_config.rs | 64 +++---------------- crates/eww/src/main.rs | 11 ++-- crates/eww/src/server.rs | 6 +- crates/yuck/src/config/config.rs | 82 +++++++++++++++++++------ crates/yuck/src/config/file_provider.rs | 69 +++++++++++++++++++++ crates/yuck/src/config/mod.rs | 1 + crates/yuck/src/config/test.rs | 9 +-- crates/yuck/src/error.rs | 6 +- crates/yuck/src/format_diagnostic.rs | 9 +++ crates/yuck/src/parser/mod.rs | 9 +-- 13 files changed, 193 insertions(+), 95 deletions(-) create mode 100644 crates/yuck/src/config/file_provider.rs diff --git a/Cargo.lock b/Cargo.lock index 6a62da4..33f838a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,7 @@ dependencies = [ "base64", "bincode", "cairo-sys-rs", + "codespan-reporting", "debug_stub_derive", "derive_more", "dyn-clone", diff --git a/crates/eww/Cargo.toml b/crates/eww/Cargo.toml index 2d77226..42dee41 100644 --- a/crates/eww/Cargo.toml +++ b/crates/eww/Cargo.toml @@ -67,6 +67,8 @@ wait-timeout = "0.2" notify = "5.0.0-pre.7" +codespan-reporting = "0.11" + simplexpr = { path = "../simplexpr" } yuck = { path = "../yuck" } eww_shared_util = { path = "../eww_shared_util" } diff --git a/crates/eww/src/app.rs b/crates/eww/src/app.rs index 18b2cd9..73dfbbc 100644 --- a/crates/eww/src/app.rs +++ b/crates/eww/src/app.rs @@ -14,7 +14,10 @@ use simplexpr::dynval::DynVal; use std::collections::HashMap; use tokio::sync::mpsc::UnboundedSender; use yuck::{ - config::window_geometry::{AnchorPoint, WindowGeometry}, + config::{ + file_provider::FsYuckFiles, + window_geometry::{AnchorPoint, WindowGeometry}, + }, value::Coords, }; @@ -115,10 +118,13 @@ impl App { DaemonCommand::ReloadConfigAndCss(sender) => { let mut errors = Vec::new(); - let config_result = EwwConfig::read_from_file(&self.paths.get_yuck_path()); + let mut yuck_files = FsYuckFiles::new(); + let config_result = EwwConfig::read_from_file(&mut yuck_files, &self.paths.get_yuck_path()); match config_result { Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)), - Err(e) => errors.push(e), + Err(e) => { + errors.push(e); + } } let css_result = crate::util::parse_scss_from_file(&self.paths.get_eww_scss_path()); @@ -193,6 +199,13 @@ impl App { } }; + // if let Err(err) = result { + // if let Some(ast_error) = err.root_cause().downcast_ref::() { + // println!("ast error: {:?}", ast_error); + //} else { + // dbg!(err.root_cause()); + //} + crate::print_result_err!("while handling event", &result); } diff --git a/crates/eww/src/config/eww_config.rs b/crates/eww/src/config/eww_config.rs index 1fafdcd..1e18eb3 100644 --- a/crates/eww/src/config/eww_config.rs +++ b/crates/eww/src/config/eww_config.rs @@ -1,9 +1,8 @@ use anyhow::*; use eww_shared_util::VarName; use std::collections::HashMap; -use yuck::{ - config::{script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition}, - parser::from_ast::FromAst, +use yuck::config::{ + file_provider::FsYuckFiles, script_var_definition::ScriptVarDefinition, widget_definition::WidgetDefinition, Config, }; use simplexpr::dynval::DynVal; @@ -17,30 +16,21 @@ pub struct EwwConfig { windows: HashMap, initial_variables: HashMap, script_vars: HashMap, + // files: FsYuckFiles, } impl EwwConfig { - pub fn read_from_file>(path: P) -> Result { - let content = std::fs::read_to_string(path)?; - let ast = yuck::parser::parse_toplevel(0, &content)?; - let config = yuck::config::Config::from_ast(ast)?; - Self::generate(config) - } - - pub fn generate(config: yuck::config::Config) -> Result { - let yuck::config::Config { widget_definitions, window_definitions, var_definitions, script_vars } = config; + pub fn read_from_file>(files: &mut FsYuckFiles, path: P) -> Result { + let config = Config::generate_from_main_file(files, path.as_ref().to_str().context("Failed to decode path")?)?; + let Config { widget_definitions, window_definitions, var_definitions, script_vars } = config; Ok(EwwConfig { windows: window_definitions .into_iter() - .map(|(name, window)| { - Ok(( - name, - EwwWindowDefinition::generate(&widget_definitions, window).context("Failed expand window definition")?, - )) - }) + .map(|(name, window)| Ok((name, EwwWindowDefinition::generate(&widget_definitions, window)?))) .collect::>>()?, widgets: widget_definitions, initial_variables: var_definitions.into_iter().map(|(k, v)| (k, v.initial_value)).collect(), script_vars, + // files, }) } @@ -71,41 +61,3 @@ impl EwwConfig { &self.widgets } } - -// Raw Eww configuration, before expanding widget usages. -//#[derive(Debug, Clone)] -// pub struct RawEwwConfig { -// widgets: HashMap, -// windows: HashMap, -// initial_variables: HashMap, -// script_vars: HashMap, -// pub filepath: PathBuf, -//} - -// impl RawEwwConfig { -// pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec) -> Result { -// 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) -//} diff --git a/crates/eww/src/main.rs b/crates/eww/src/main.rs index 527e222..d75f694 100644 --- a/crates/eww/src/main.rs +++ b/crates/eww/src/main.rs @@ -105,7 +105,7 @@ fn check_server_running(socket_path: impl AsRef) -> bool { response.is_some() } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct EwwPaths { log_file: PathBuf, ipc_socket_file: PathBuf, @@ -115,11 +115,9 @@ pub struct EwwPaths { impl EwwPaths { pub fn from_config_dir>(config_dir: P) -> Result { let config_dir = config_dir.as_ref(); - let config_dir = if config_dir.is_file() { - config_dir.parent().context("Given config file did not have a parent directory")? - } else { - config_dir - }; + if config_dir.is_file() { + bail!("Please provide the path to the config directory, not a file within it") + } if !config_dir.exists() { bail!("Configuration directory {} does not exist", config_dir.display()); @@ -166,6 +164,7 @@ impl EwwPaths { self.config_dir.join("eww.yuck") } + pub fn get_eww_scss_path(&self) -> PathBuf { self.config_dir.join("eww.scss") } diff --git a/crates/eww/src/server.rs b/crates/eww/src/server.rs index f2c8127..77a6dc5 100644 --- a/crates/eww/src/server.rs +++ b/crates/eww/src/server.rs @@ -1,5 +1,6 @@ use crate::{app, config, eww_state::*, ipc_server, script_var_handler, util, EwwPaths}; use anyhow::*; +use yuck::config::file_provider::FsYuckFiles; use std::{collections::HashMap, os::unix::io::AsRawFd, path::Path}; use tokio::sync::mpsc::*; @@ -27,7 +28,10 @@ pub fn initialize_server(paths: EwwPaths) -> Result<()> { .with_context(|| format!("Failed to change working directory to {}", paths.get_config_dir().display()))?; log::info!("Loading paths: {}", &paths); - let eww_config = config::EwwConfig::read_from_file(&paths.get_yuck_path())?; + + let mut yuck_files = FsYuckFiles::new(); + + let eww_config = config::EwwConfig::read_from_file(&mut yuck_files, &paths.get_yuck_path())?; gtk::init()?; diff --git a/crates/yuck/src/config/config.rs b/crates/yuck/src/config/config.rs index e893876..bb5bce9 100644 --- a/crates/yuck/src/config/config.rs +++ b/crates/yuck/src/config/config.rs @@ -1,10 +1,15 @@ use std::collections::HashMap; +use codespan_reporting::files::SimpleFiles; use simplexpr::SimplExpr; use super::{ - script_var_definition::ScriptVarDefinition, var_definition::VarDefinition, widget_definition::WidgetDefinition, - widget_use::WidgetUse, window_definition::WindowDefinition, + file_provider::{FilesError, YuckFiles}, + script_var_definition::ScriptVarDefinition, + var_definition::VarDefinition, + widget_definition::WidgetDefinition, + widget_use::WidgetUse, + window_definition::WindowDefinition, }; use crate::{ config::script_var_definition::{PollScriptVar, TailScriptVar}, @@ -17,7 +22,25 @@ use crate::{ }; use eww_shared_util::{AttrName, Span, VarName}; +#[derive(Debug, PartialEq, Eq, Clone, serde::Serialize)] +pub struct Include { + pub path: String, + pub path_span: Span, +} + +impl FromAstElementContent for Include { + fn get_element_name() -> &'static str { + "include" + } + + fn from_tail>(span: Span, mut iter: AstIterator) -> AstResult { + let (path_span, path) = iter.expect_literal()?; + Ok(Include { path: path.to_string(), path_span }) + } +} + pub enum TopLevel { + Include(Include), VarDefinition(VarDefinition), ScriptVarDefinition(ScriptVarDefinition), WidgetDefinition(WidgetDefinition), @@ -30,6 +53,7 @@ impl FromAst for TopLevel { let mut iter = e.try_ast_iter()?; let (sym_span, element_name) = iter.expect_symbol()?; Ok(match element_name.as_str() { + x if x == Include::get_element_name() => Self::Include(Include::from_tail(span, iter)?), 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() => { @@ -52,31 +76,49 @@ pub struct Config { pub script_vars: HashMap, } -impl FromAst for Config { - fn from_ast(e: Ast) -> AstResult { - let list = e.as_list()?; +impl Config { + fn append_toplevel(&mut self, files: &mut impl YuckFiles, toplevel: TopLevel) -> AstResult<()> { + match toplevel { + TopLevel::VarDefinition(x) => { + self.var_definitions.insert(x.name.clone(), x); + } + TopLevel::ScriptVarDefinition(x) => { + self.script_vars.insert(x.name().clone(), x); + } + TopLevel::WidgetDefinition(x) => { + self.widget_definitions.insert(x.name.clone(), x); + } + TopLevel::WindowDefinition(x) => { + self.window_definitions.insert(x.name.clone(), x); + } + TopLevel::Include(include) => { + let (file_id, toplevels) = files.load(&include.path).map_err(|err| match err { + FilesError::IoError(_) => AstError::IncludedFileNotFound(include), + FilesError::AstError(x) => x, + })?; + for element in toplevels { + self.append_toplevel(files, TopLevel::from_ast(element)?)?; + } + } + } + Ok(()) + } + + pub fn generate(files: &mut impl YuckFiles, elements: Vec) -> AstResult { let mut config = Self { widget_definitions: HashMap::new(), window_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); - } - TopLevel::WindowDefinition(x) => { - config.window_definitions.insert(x.name.clone(), x); - } - } + for element in elements { + config.append_toplevel(files, TopLevel::from_ast(element)?)?; } Ok(config) } + + pub fn generate_from_main_file(files: &mut impl YuckFiles, path: &str) -> AstResult { + let (span, top_levels) = files.load(path).map_err(|err| AstError::Other(None, Box::new(err)))?; + Self::generate(files, top_levels) + } } diff --git a/crates/yuck/src/config/file_provider.rs b/crates/yuck/src/config/file_provider.rs new file mode 100644 index 0000000..cfc5588 --- /dev/null +++ b/crates/yuck/src/config/file_provider.rs @@ -0,0 +1,69 @@ +use std::{collections::HashMap, path::PathBuf}; + +use codespan_reporting::files::{Files, SimpleFile, SimpleFiles}; +use eww_shared_util::Span; + +use crate::{ + error::{AstError, AstResult}, + parser::ast::Ast, +}; + +#[derive(thiserror::Error, Debug)] +pub enum FilesError { + #[error(transparent)] + IoError(#[from] std::io::Error), + + #[error(transparent)] + AstError(#[from] AstError), +} + +pub trait YuckFiles { + fn load(&mut self, path: &str) -> Result<(Span, Vec), FilesError>; +} + +#[derive(Debug, Clone)] +pub struct FsYuckFiles { + files: SimpleFiles, +} + +impl FsYuckFiles { + pub fn new() -> Self { + Self { files: SimpleFiles::new() } + } +} + +impl YuckFiles for FsYuckFiles { + fn load(&mut self, path: &str) -> Result<(Span, Vec), FilesError> { + let path = PathBuf::from(path); + + let file_content = std::fs::read_to_string(&path)?; + let file_id = self.files.add(path.display().to_string(), file_content.to_string()); + Ok(crate::parser::parse_toplevel(file_id, file_content)?) + } +} + +impl<'a> Files<'a> for FsYuckFiles { + type FileId = usize; + type Name = String; + type Source = &'a str; + + fn name(&self, id: Self::FileId) -> Result { + self.files.name(id) + } + + fn source(&'a self, id: Self::FileId) -> Result { + self.files.source(id) + } + + fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result { + self.files.line_index(id, byte_index) + } + + fn line_range( + &self, + id: Self::FileId, + line_index: usize, + ) -> Result, codespan_reporting::files::Error> { + self.files.line_range(id, line_index) + } +} diff --git a/crates/yuck/src/config/mod.rs b/crates/yuck/src/config/mod.rs index 3338d5d..8275305 100644 --- a/crates/yuck/src/config/mod.rs +++ b/crates/yuck/src/config/mod.rs @@ -2,6 +2,7 @@ pub mod attributes; pub mod backend_window_options; pub mod config; pub mod config_parse_error; +pub mod file_provider; pub mod script_var_definition; #[cfg(test)] mod test; diff --git a/crates/yuck/src/config/test.rs b/crates/yuck/src/config/test.rs index 677f4b3..fcf0963 100644 --- a/crates/yuck/src/config/test.rs +++ b/crates/yuck/src/config/test.rs @@ -25,8 +25,9 @@ fn test_config() { let lexer = Lexer::new(0, input.to_string()); let p = parser::parser::ToplevelParser::new(); let (span, parse_result) = p.parse(0, lexer).unwrap(); - let config = Config::from_ast(Ast::List(span, parse_result)); - insta::with_settings!({sort_maps => true}, { - insta::assert_ron_snapshot!(config.unwrap()); - }); + // TODO implement another YuckFiles thing to test here again + // let config = Config::from_ast(Ast::List(span, parse_result)); + // insta::with_settings!({sort_maps => true}, { + // insta::assert_ron_snapshot!(config.unwrap()); + //}); } diff --git a/crates/yuck/src/error.rs b/crates/yuck/src/error.rs index f973ebd..2c4662b 100644 --- a/crates/yuck/src/error.rs +++ b/crates/yuck/src/error.rs @@ -1,5 +1,5 @@ use crate::{ - config::{attributes::AttrError, validate::ValidationError}, + config::{attributes::AttrError, config::Include, validate::ValidationError}, parser::{ ast::{Ast, AstType}, lexer, parse_error, @@ -25,6 +25,9 @@ pub enum AstError { #[error("Expected element {1}, but read {2}")] MismatchedElementName(Span, String, String), + #[error("Included file not found {}", .0.path)] + IncludedFileNotFound(Include), + #[error(transparent)] ConversionError(#[from] dynval::ConversionError), @@ -56,6 +59,7 @@ impl AstError { AstError::AttrError(err) => Some(err.span()), AstError::Other(span, ..) => *span, AstError::ConversionError(err) => err.value.span().map(|x| x.into()), + AstError::IncludedFileNotFound(include) => Some(include.path_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)), } diff --git a/crates/yuck/src/format_diagnostic.rs b/crates/yuck/src/format_diagnostic.rs index c724f74..f244344 100644 --- a/crates/yuck/src/format_diagnostic.rs +++ b/crates/yuck/src/format_diagnostic.rs @@ -8,6 +8,10 @@ use crate::error::AstError; use super::parser::parse_error; use eww_shared_util::{AttrName, Span, VarName}; +fn span_to_secondary_label(span: Span) -> Label { + Label::secondary(span.2, span.0..span.1) +} + macro_rules! gen_diagnostic { ( $(msg = $msg:expr)? @@ -82,6 +86,11 @@ impl ToDiagnostic for AstError { AstError::ConversionError(err) => conversion_error_to_diagnostic(err, span), AstError::Other(_, source) => gen_diagnostic!(source, span), AstError::AttrError(source) => gen_diagnostic!(source, span), + AstError::IncludedFileNotFound(include) => gen_diagnostic!( + msg = format!("Included file `{}` not found", include.path), + label = include.path_span => "Included here", + ), + AstError::ValidationError(_) => todo!(), } } else { diff --git a/crates/yuck/src/parser/mod.rs b/crates/yuck/src/parser/mod.rs index 497d428..8e5499f 100644 --- a/crates/yuck/src/parser/mod.rs +++ b/crates/yuck/src/parser/mod.rs @@ -1,3 +1,4 @@ +use eww_shared_util::Span; use lalrpop_util::lalrpop_mod; use super::error::{AstError, AstResult}; @@ -25,11 +26,11 @@ pub fn parse_string(file_id: usize, s: &str) -> AstResult { parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e)) } -/// Parse multiple toplevel nodes into an [Ast::List] -pub fn parse_toplevel(file_id: usize, s: &str) -> AstResult { - let lexer = lexer::Lexer::new(file_id, s.to_string()); +/// Parse multiple toplevel nodes into a list of [Ast] +pub fn parse_toplevel(file_id: usize, s: String) -> AstResult<(Span, Vec)> { + let lexer = lexer::Lexer::new(file_id, s); let parser = parser::ToplevelParser::new(); - parser.parse(file_id, lexer).map(|(span, nodes)| Ast::List(span, nodes)).map_err(|e| AstError::from_parse_error(file_id, e)) + parser.parse(file_id, lexer).map_err(|e| AstError::from_parse_error(file_id, e)) } macro_rules! test_parser {