Implement YuckFiles for include handling
This commit is contained in:
parent
4bf3c6fd63
commit
618e652389
13 changed files with 193 additions and 95 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -433,6 +433,7 @@ dependencies = [
|
|||
"base64",
|
||||
"bincode",
|
||||
"cairo-sys-rs",
|
||||
"codespan-reporting",
|
||||
"debug_stub_derive",
|
||||
"derive_more",
|
||||
"dyn-clone",
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
//}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()?;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
69
crates/yuck/src/config/file_provider.rs
Normal file
69
crates/yuck/src/config/file_provider.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
//});
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue