Implement YuckFiles for include handling

This commit is contained in:
elkowar 2021-07-22 21:06:22 +02:00
parent 4bf3c6fd63
commit 618e652389
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
13 changed files with 193 additions and 95 deletions

1
Cargo.lock generated
View file

@ -433,6 +433,7 @@ dependencies = [
"base64",
"bincode",
"cairo-sys-rs",
"codespan-reporting",
"debug_stub_derive",
"derive_more",
"dyn-clone",

View file

@ -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" }

View file

@ -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::<AstError>() {
// println!("ast error: {:?}", ast_error);
//} else {
// dbg!(err.root_cause());
//}
crate::print_result_err!("while handling event", &result);
}

View file

@ -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<String, EwwWindowDefinition>,
initial_variables: HashMap<VarName, DynVal>,
script_vars: HashMap<VarName, ScriptVarDefinition>,
// files: FsYuckFiles,
}
impl EwwConfig {
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
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<Self> {
let yuck::config::Config { widget_definitions, window_definitions, var_definitions, script_vars } = config;
pub fn read_from_file<P: AsRef<std::path::Path>>(files: &mut FsYuckFiles, path: P) -> Result<Self> {
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::<Result<HashMap<_, _>>>()?,
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<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)
//}

View file

@ -105,7 +105,7 @@ fn check_server_running(socket_path: impl AsRef<Path>) -> 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<P: AsRef<Path>>(config_dir: P) -> Result<Self> {
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")
}

View file

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

View file

@ -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<I: Iterator<Item = Ast>>(span: Span, mut iter: AstIterator<I>) -> AstResult<Self> {
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<VarName, ScriptVarDefinition>,
}
impl FromAst for Config {
fn from_ast(e: Ast) -> AstResult<Self> {
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<Ast>) -> AstResult<Self> {
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<Self> {
let (span, top_levels) = files.load(path).map_err(|err| AstError::Other(None, Box::new(err)))?;
Self::generate(files, top_levels)
}
}

View file

@ -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<Ast>), FilesError>;
}
#[derive(Debug, Clone)]
pub struct FsYuckFiles {
files: SimpleFiles<String, String>,
}
impl FsYuckFiles {
pub fn new() -> Self {
Self { files: SimpleFiles::new() }
}
}
impl YuckFiles for FsYuckFiles {
fn load(&mut self, path: &str) -> Result<(Span, Vec<Ast>), 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::Name, codespan_reporting::files::Error> {
self.files.name(id)
}
fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {
self.files.source(id)
}
fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<usize, codespan_reporting::files::Error> {
self.files.line_index(id, byte_index)
}
fn line_range(
&self,
id: Self::FileId,
line_index: usize,
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
self.files.line_range(id, line_index)
}
}

View file

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

View file

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

View file

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

View file

@ -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<usize> {
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 {

View file

@ -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<Ast> {
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<Ast> {
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<Ast>)> {
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 {