Improve CSS error handling by parsing GTK error output (see #446)

This commit is contained in:
elkowar 2022-09-04 16:16:37 +02:00
parent 58907eac4b
commit 7623e7e692
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
13 changed files with 213 additions and 198 deletions

16
Cargo.lock generated
View file

@ -15,9 +15,9 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -771,9 +771,9 @@ dependencies = [
[[package]] [[package]]
name = "grass" name = "grass"
version = "0.11.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34c6284b9a420f456146e1a10e602b82ea251f10b40854bcf04598b7121a3910" checksum = "bc5bedc3dbd71dcdd41900e1f58e4d431fa69dd67c04ae1f86ae1a0339edd849"
dependencies = [ dependencies = [
"beef", "beef",
"clap 2.34.0", "clap 2.34.0",
@ -1856,9 +1856,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.26.1" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "621609553b14bca49448b3c97e625d7187980cc2a42fd169b4c3b306dcc4a7e9" checksum = "4ae2421f3e16b3afd4aa692d23b83d0ba42ee9b0081d5deeb7d21428d7195fb1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"core-foundation-sys", "core-foundation-sys",
@ -2012,9 +2012,9 @@ dependencies = [
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]] [[package]]
name = "unescape" name = "unescape"

View file

@ -9,9 +9,11 @@ use crate::{
*, *,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use eww_shared_util::VarName; use codespan_reporting::files::Files;
use eww_shared_util::{Span, VarName};
use glib::ObjectExt; use glib::ObjectExt;
use itertools::Itertools; use itertools::Itertools;
use once_cell::sync::Lazy;
use simplexpr::dynval::DynVal; use simplexpr::dynval::DynVal;
use std::{ use std::{
cell::RefCell, cell::RefCell,
@ -26,6 +28,8 @@ use yuck::{
window_definition::WindowDefinition, window_definition::WindowDefinition,
window_geometry::{AnchorPoint, WindowGeometry}, window_geometry::{AnchorPoint, WindowGeometry},
}, },
error::DiagError,
gen_diagnostic,
value::Coords, value::Coords,
}; };
@ -145,9 +149,15 @@ impl App {
if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) { if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) {
errors.push(e) errors.push(e)
} }
let css_result = crate::config::scss::parse_scss_from_file(&self.paths.get_eww_scss_path()); match crate::config::scss::parse_scss_from_file(&self.paths.get_eww_scss_path()) {
if let Err(e) = css_result.and_then(|css| self.load_css(&css)) { Ok((file_id, css)) => {
errors.push(e) if let Err(e) = self.load_css(file_id, &css) {
errors.push(anyhow!(e));
}
}
Err(e) => {
errors.push(e);
}
} }
sender.respond_with_error_list(errors)?; sender.respond_with_error_list(errors)?;
@ -400,9 +410,26 @@ impl App {
Ok(()) Ok(())
} }
pub fn load_css(&mut self, css: &str) -> Result<()> { /// Load a given CSS string into the gtk css provider, returning a nicely formatted [`DiagError`] when GTK errors out
self.css_provider.load_from_data(css.as_bytes())?; pub fn load_css(&mut self, file_id: usize, css: &str) -> Result<()> {
Ok(()) if let Err(err) = self.css_provider.load_from_data(css.as_bytes()) {
static PATTERN: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"[^:]*:(\d+):(\d+)(.*)$").unwrap());
let nice_error_option: Option<_> = try {
let captures = PATTERN.captures(&err.message())?;
let line = captures.get(1).unwrap().as_str().parse::<usize>().ok()?;
let msg = captures.get(3).unwrap().as_str();
let db = error_handling_ctx::FILE_DATABASE.read().ok()?;
let line_range = db.line_range(file_id, line - 1).ok()?;
let span = Span(line_range.start, line_range.end - 1, file_id);
DiagError(gen_diagnostic!(msg, span))
};
match nice_error_option {
Some(error) => Err(anyhow!(error)),
None => Err(anyhow!("CSS error: {}", err.message())),
}
} else {
Ok(())
}
} }
} }

View file

@ -3,8 +3,8 @@ use eww_shared_util::VarName;
use std::collections::HashMap; use std::collections::HashMap;
use yuck::{ use yuck::{
config::{ config::{
file_provider::YuckFiles, script_var_definition::ScriptVarDefinition, validate::ValidationError, script_var_definition::ScriptVarDefinition, validate::ValidationError, widget_definition::WidgetDefinition,
widget_definition::WidgetDefinition, window_definition::WindowDefinition, Config, window_definition::WindowDefinition, Config,
}, },
error::DiagError, error::DiagError,
format_diagnostic::ToDiagnostic, format_diagnostic::ToDiagnostic,
@ -12,7 +12,7 @@ use yuck::{
use simplexpr::dynval::DynVal; use simplexpr::dynval::DynVal;
use crate::{config::inbuilt, error_handling_ctx, paths::EwwPaths, widgets::widget_definitions}; use crate::{config::inbuilt, error_handling_ctx, file_database::FileDatabase, paths::EwwPaths, widgets::widget_definitions};
use super::script_var; use super::script_var;
@ -20,7 +20,7 @@ use super::script_var;
/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`]. /// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].
pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> { pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> {
error_handling_ctx::clear_files(); error_handling_ctx::clear_files();
EwwConfig::read_from_dir(&mut error_handling_ctx::YUCK_FILES.write().unwrap(), eww_paths) EwwConfig::read_from_dir(&mut error_handling_ctx::FILE_DATABASE.write().unwrap(), eww_paths)
} }
/// Eww configuration structure. /// Eww configuration structure.
@ -49,7 +49,7 @@ impl Default for EwwConfig {
impl EwwConfig { impl EwwConfig {
/// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file. /// Load an [`EwwConfig`] from the config dir of the given [`crate::EwwPaths`], reading the main config file.
pub fn read_from_dir(files: &mut YuckFiles, eww_paths: &EwwPaths) -> Result<Self> { pub fn read_from_dir(files: &mut FileDatabase, eww_paths: &EwwPaths) -> Result<Self> {
let yuck_path = eww_paths.get_yuck_path(); let yuck_path = eww_paths.get_yuck_path();
if !yuck_path.exists() { if !yuck_path.exists() {
bail!("The configuration file `{}` does not exist", yuck_path.display()); bail!("The configuration file `{}` does not exist", yuck_path.display());

View file

@ -2,15 +2,20 @@ use std::path::Path;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use crate::util::replace_env_var_references; use crate::{error_handling_ctx, util::replace_env_var_references};
/// read an scss file, replace all environment variable references within it and /// read an scss file, replace all environment variable references within it and
/// then parse it into css. /// then parse it into css.
pub fn parse_scss_from_file(path: &Path) -> anyhow::Result<String> { /// Also adds the CSS to the [`crate::file_database::FileDatabase`]
pub fn parse_scss_from_file(path: &Path) -> anyhow::Result<(usize, String)> {
let config_dir = path.parent().context("Given SCSS file has no parent directory?!")?; let config_dir = path.parent().context("Given SCSS file has no parent directory?!")?;
let scss_file_content = let scss_file_content =
std::fs::read_to_string(path).with_context(|| format!("Given SCSS-file doesn't exist! {}", path.display()))?; std::fs::read_to_string(path).with_context(|| format!("Given SCSS-file doesn't exist! {}", path.display()))?;
let file_content = replace_env_var_references(scss_file_content); let file_content = replace_env_var_references(scss_file_content);
let grass_config = grass::Options::default().load_path(config_dir); let grass_config = grass::Options::default().load_path(config_dir);
grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("Encountered SCSS parsing error: {}", err)) let css = grass::from_string(file_content, &grass_config).map_err(|err| anyhow!("SCSS parsing error: {}", err))?;
let mut file_db = error_handling_ctx::FILE_DATABASE.write().unwrap();
let file_id = file_db.insert_string(path.display().to_string(), css.clone())?;
Ok((file_id, css))
} }

View file

@ -10,31 +10,23 @@ use codespan_reporting::{
use eww_shared_util::Span; use eww_shared_util::Span;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use simplexpr::{dynval::ConversionError, eval::EvalError}; use simplexpr::{dynval::ConversionError, eval::EvalError};
use yuck::{ use yuck::{config::validate::ValidationError, error::DiagError, format_diagnostic::ToDiagnostic};
config::{file_provider::YuckFiles, validate::ValidationError},
error::DiagError,
format_diagnostic::ToDiagnostic,
};
pub static YUCK_FILES: Lazy<Arc<RwLock<YuckFiles>>> = Lazy::new(|| Arc::new(RwLock::new(YuckFiles::new()))); use crate::file_database::FileDatabase;
pub static FILE_DATABASE: Lazy<Arc<RwLock<FileDatabase>>> = Lazy::new(|| Arc::new(RwLock::new(FileDatabase::new())));
pub fn clear_files() { pub fn clear_files() {
*YUCK_FILES.write().unwrap() = YuckFiles::new(); *FILE_DATABASE.write().unwrap() = FileDatabase::new();
} }
pub fn print_error(err: anyhow::Error) { pub fn print_error(err: anyhow::Error) {
match anyhow_err_to_diagnostic(&err) { match anyhow_err_to_diagnostic(&err) {
Some(diag) => match stringify_diagnostic(diag) { Some(diag) => match stringify_diagnostic(diag) {
Ok(diag) => { Ok(diag) => eprintln!("{}", diag),
eprintln!("{}", diag); Err(_) => log::error!("{:?}", err),
}
Err(_) => {
log::error!("{:?}", err);
}
}, },
None => { None => log::error!("{:?}", err),
log::error!("{:?}", err);
}
} }
} }
@ -69,7 +61,7 @@ pub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnostic::Diag
config.chars.note_bullet = '→'; config.chars.note_bullet = '→';
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut writer = term::termcolor::Ansi::new(&mut buf); let mut writer = term::termcolor::Ansi::new(&mut buf);
let files = YUCK_FILES.read().unwrap(); let files = FILE_DATABASE.read().unwrap();
term::emit(&mut writer, &config, &*files, &diagnostic)?; term::emit(&mut writer, &config, &*files, &diagnostic)?;
Ok(String::from_utf8(buf)?) Ok(String::from_utf8(buf)?)
} }

View file

@ -0,0 +1,131 @@
use std::collections::HashMap;
use codespan_reporting::files::Files;
use eww_shared_util::Span;
use yuck::{
config::file_provider::{FilesError, YuckFileProvider},
error::DiagError,
parser::ast::Ast,
};
#[derive(Debug, Clone, Default)]
pub struct FileDatabase {
files: HashMap<usize, CodeFile>,
latest_id: usize,
}
impl FileDatabase {
pub fn new() -> Self {
Self::default()
}
fn get_file(&self, id: usize) -> Result<&CodeFile, codespan_reporting::files::Error> {
self.files.get(&id).ok_or(codespan_reporting::files::Error::FileMissing)
}
fn insert_code_file(&mut self, file: CodeFile) -> usize {
let file_id = self.latest_id;
self.files.insert(file_id, file);
self.latest_id += 1;
file_id
}
pub fn insert_string(&mut self, name: String, content: String) -> Result<usize, DiagError> {
let line_starts = codespan_reporting::files::line_starts(&content).collect();
let code_file = CodeFile { name, line_starts, source_len_bytes: content.len(), source: CodeSource::Literal(content) };
let file_id = self.insert_code_file(code_file);
Ok(file_id)
}
}
impl YuckFileProvider for FileDatabase {
fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError> {
let file_content = std::fs::read_to_string(&path)?;
let line_starts = codespan_reporting::files::line_starts(&file_content).collect();
let code_file = CodeFile {
name: path.display().to_string(),
line_starts,
source_len_bytes: file_content.len(),
source: CodeSource::File(path),
};
let file_id = self.insert_code_file(code_file);
Ok(yuck::parser::parse_toplevel(file_id, file_content)?)
}
fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError> {
let file_id = self.insert_string(name, content.clone())?;
yuck::parser::parse_toplevel(file_id, content)
}
fn unload(&mut self, id: usize) {
self.files.remove(&id);
}
}
impl<'a> Files<'a> for FileDatabase {
type FileId = usize;
type Name = &'a str;
type Source = String;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
Ok(&self.get_file(id)?.name)
}
fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {
self.get_file(id)?.source.read_content().map_err(codespan_reporting::files::Error::Io)
}
fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<usize, codespan_reporting::files::Error> {
Ok(self.get_file(id)?.line_starts.binary_search(&byte_index).unwrap_or_else(|next_line| next_line - 1))
}
fn line_range(
&self,
id: Self::FileId,
line_index: usize,
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
let file = self.get_file(id)?;
let line_start = file.line_start(line_index)?;
let next_line_start = file.line_start(line_index + 1)?;
Ok(line_start..next_line_start)
}
}
#[derive(Clone, Debug)]
struct CodeFile {
name: String,
line_starts: Vec<usize>,
source: CodeSource,
source_len_bytes: usize,
}
impl CodeFile {
/// Return the starting byte index of the line with the specified line index.
/// Convenience method that already generates errors if necessary.
fn line_start(&self, line_index: usize) -> Result<usize, codespan_reporting::files::Error> {
use std::cmp::Ordering;
match line_index.cmp(&self.line_starts.len()) {
Ordering::Less => Ok(self.line_starts.get(line_index).cloned().expect("failed despite previous check")),
Ordering::Equal => Ok(self.source_len_bytes),
Ordering::Greater => {
Err(codespan_reporting::files::Error::LineTooLarge { given: line_index, max: self.line_starts.len() - 1 })
}
}
}
}
#[derive(Clone, Debug)]
enum CodeSource {
File(std::path::PathBuf),
Literal(String),
}
impl CodeSource {
fn read_content(&self) -> std::io::Result<String> {
match self {
CodeSource::File(path) => Ok(std::fs::read_to_string(path)?),
CodeSource::Literal(x) => Ok(x.to_string()),
}
}
}

View file

@ -26,6 +26,7 @@ mod config;
mod daemon_response; mod daemon_response;
mod display_backend; mod display_backend;
mod error_handling_ctx; mod error_handling_ctx;
mod file_database;
mod geometry; mod geometry;
mod ipc_server; mod ipc_server;
mod opts; mod opts;

View file

@ -83,8 +83,10 @@ pub fn initialize_server(paths: EwwPaths, action: Option<DaemonCommand>, should_
gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION); gtk::StyleContext::add_provider_for_screen(&screen, &app.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
} }
if let Ok(eww_css) = config::scss::parse_scss_from_file(&app.paths.get_eww_scss_path()) { if let Ok((file_id, css)) = config::scss::parse_scss_from_file(&app.paths.get_eww_scss_path()) {
app.load_css(&eww_css)?; if let Err(e) = app.load_css(file_id, &css) {
error_handling_ctx::print_error(e);
}
} }
// initialize all the handlers and tasks running asyncronously // initialize all the handlers and tasks running asyncronously

View file

@ -23,6 +23,7 @@ use std::{
time::Duration, time::Duration,
}; };
use yuck::{ use yuck::{
config::file_provider::YuckFileProvider,
error::{DiagError, DiagResult}, error::{DiagError, DiagResult},
format_diagnostic::{span_to_secondary_label, DiagnosticExt}, format_diagnostic::{span_to_secondary_label, DiagnosticExt},
gen_diagnostic, gen_diagnostic,
@ -868,8 +869,8 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
if !content.is_empty() { if !content.is_empty() {
let content_widget_use: DiagResult<_> = try { let content_widget_use: DiagResult<_> = try {
let ast = { let ast = {
let mut yuck_files = error_handling_ctx::YUCK_FILES.write().unwrap(); let mut yuck_files = error_handling_ctx::FILE_DATABASE.write().unwrap();
let (span, asts) = yuck_files.load_str("<literal-content>".to_string(), content)?; let (span, asts) = yuck_files.load_yuck_str("<literal-content>".to_string(), content)?;
if let Some(file_id) = literal_file_id.replace(Some(span.2)) { if let Some(file_id) = literal_file_id.replace(Some(span.2)) {
yuck_files.unload(file_id); yuck_files.unload(file_id);
} }

View file

@ -8,7 +8,7 @@ use itertools::Itertools;
use simplexpr::SimplExpr; use simplexpr::SimplExpr;
use super::{ use super::{
file_provider::{FilesError, YuckFiles}, file_provider::{FilesError, YuckFileProvider},
script_var_definition::ScriptVarDefinition, script_var_definition::ScriptVarDefinition,
validate::ValidationError, validate::ValidationError,
var_definition::VarDefinition, var_definition::VarDefinition,
@ -98,7 +98,7 @@ pub struct Config {
} }
impl Config { impl Config {
fn append_toplevel(&mut self, files: &mut YuckFiles, toplevel: TopLevel) -> DiagResult<()> { fn append_toplevel(&mut self, files: &mut impl YuckFileProvider, toplevel: TopLevel) -> DiagResult<()> {
match toplevel { match toplevel {
TopLevel::VarDefinition(x) => { TopLevel::VarDefinition(x) => {
if self.var_definitions.contains_key(&x.name) || self.script_vars.contains_key(&x.name) { if self.var_definitions.contains_key(&x.name) || self.script_vars.contains_key(&x.name) {
@ -127,7 +127,7 @@ impl Config {
self.window_definitions.insert(x.name.clone(), x); self.window_definitions.insert(x.name.clone(), x);
} }
TopLevel::Include(include) => { TopLevel::Include(include) => {
let (file_id, toplevels) = files.load_file(PathBuf::from(&include.path)).map_err(|err| match err { let (file_id, toplevels) = files.load_yuck_file(PathBuf::from(&include.path)).map_err(|err| match err {
FilesError::IoError(_) => DiagError(gen_diagnostic! { FilesError::IoError(_) => DiagError(gen_diagnostic! {
msg = format!("Included file `{}` not found", include.path), msg = format!("Included file `{}` not found", include.path),
label = include.path_span => "Included here", label = include.path_span => "Included here",
@ -142,7 +142,7 @@ impl Config {
Ok(()) Ok(())
} }
pub fn generate(files: &mut YuckFiles, elements: Vec<Ast>) -> DiagResult<Self> { pub fn generate(files: &mut impl YuckFileProvider, elements: Vec<Ast>) -> DiagResult<Self> {
let mut config = Self { let mut config = Self {
widget_definitions: HashMap::new(), widget_definitions: HashMap::new(),
window_definitions: HashMap::new(), window_definitions: HashMap::new(),
@ -155,8 +155,8 @@ impl Config {
Ok(config) Ok(config)
} }
pub fn generate_from_main_file(files: &mut YuckFiles, path: impl AsRef<Path>) -> DiagResult<Self> { pub fn generate_from_main_file(files: &mut impl YuckFileProvider, path: impl AsRef<Path>) -> DiagResult<Self> {
let (span, top_levels) = files.load_file(path.as_ref().to_path_buf()).map_err(|err| match err { let (span, top_levels) = files.load_yuck_file(path.as_ref().to_path_buf()).map_err(|err| match err {
FilesError::IoError(err) => DiagError(gen_diagnostic!(err)), FilesError::IoError(err) => DiagError(gen_diagnostic!(err)),
FilesError::DiagError(x) => x, FilesError::DiagError(x) => x,
})?; })?;

View file

@ -17,120 +17,8 @@ pub enum FilesError {
DiagError(#[from] DiagError), DiagError(#[from] DiagError),
} }
#[derive(Clone, Debug)] pub trait YuckFileProvider {
pub enum YuckSource { fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError>;
File(std::path::PathBuf), fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError>;
Literal(String), fn unload(&mut self, id: usize);
}
impl YuckSource {
pub fn read_content(&self) -> std::io::Result<String> {
match self {
YuckSource::File(path) => Ok(std::fs::read_to_string(path)?),
YuckSource::Literal(x) => Ok(x.to_string()),
}
}
}
#[derive(Clone, Debug)]
pub struct YuckFile {
name: String,
line_starts: Vec<usize>,
source: YuckSource,
source_len_bytes: usize,
}
impl YuckFile {
/// Return the starting byte index of the line with the specified line index.
/// Convenience method that already generates errors if necessary.
fn line_start(&self, line_index: usize) -> Result<usize, codespan_reporting::files::Error> {
use std::cmp::Ordering;
match line_index.cmp(&self.line_starts.len()) {
Ordering::Less => Ok(self.line_starts.get(line_index).cloned().expect("failed despite previous check")),
Ordering::Equal => Ok(self.source_len_bytes),
Ordering::Greater => {
Err(codespan_reporting::files::Error::LineTooLarge { given: line_index, max: self.line_starts.len() - 1 })
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct YuckFiles {
files: HashMap<usize, YuckFile>,
latest_id: usize,
}
impl YuckFiles {
pub fn new() -> Self {
Self::default()
}
}
impl YuckFiles {
pub fn get_file(&self, id: usize) -> Result<&YuckFile, codespan_reporting::files::Error> {
self.files.get(&id).ok_or(codespan_reporting::files::Error::FileMissing)
}
fn insert_file(&mut self, file: YuckFile) -> usize {
let file_id = self.latest_id;
self.files.insert(file_id, file);
self.latest_id += 1;
file_id
}
pub fn load_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError> {
let file_content = std::fs::read_to_string(&path)?;
let line_starts = codespan_reporting::files::line_starts(&file_content).collect();
let yuck_file = YuckFile {
name: path.display().to_string(),
line_starts,
source_len_bytes: file_content.len(),
source: YuckSource::File(path),
};
let file_id = self.insert_file(yuck_file);
Ok(crate::parser::parse_toplevel(file_id, file_content)?)
}
pub fn load_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError> {
let line_starts = codespan_reporting::files::line_starts(&content).collect();
let yuck_file =
YuckFile { name, line_starts, source_len_bytes: content.len(), source: YuckSource::Literal(content.to_string()) };
let file_id = self.insert_file(yuck_file);
crate::parser::parse_toplevel(file_id, content)
}
pub fn unload(&mut self, id: usize) {
self.files.remove(&id);
}
}
impl<'a> Files<'a> for YuckFiles {
type FileId = usize;
type Name = &'a str;
type Source = String;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
Ok(&self.get_file(id)?.name)
}
fn source(&'a self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {
self.get_file(id)?.source.read_content().map_err(codespan_reporting::files::Error::Io)
}
fn line_index(&self, id: Self::FileId, byte_index: usize) -> Result<usize, codespan_reporting::files::Error> {
Ok(self.get_file(id)?.line_starts.binary_search(&byte_index).unwrap_or_else(|next_line| next_line - 1))
}
fn line_range(
&self,
id: Self::FileId,
line_index: usize,
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
let file = self.get_file(id)?;
let line_start = file.line_start(line_index)?;
let next_line_start = file.line_start(line_index + 1)?;
Ok(line_start..next_line_start)
}
} }

View file

@ -4,8 +4,6 @@ pub mod config;
pub mod file_provider; pub mod file_provider;
pub mod monitor; pub mod monitor;
pub mod script_var_definition; pub mod script_var_definition;
#[cfg(test)]
mod test;
pub mod validate; pub mod validate;
pub mod var_definition; pub mod var_definition;
pub mod widget_definition; pub mod widget_definition;

View file

@ -1,30 +0,0 @@
use crate::{
config::config::Config,
parser::{self, ast::Ast, from_ast::FromAst, lexer::Lexer},
};
use super::file_provider::YuckFiles;
#[test]
fn test_config() {
let input = r#"
(defwidget bar [arg arg2]
(foo :arg "hi"))
(defvar some_var "bla")
(defpoll stuff :interval "12s" "date")
(deflisten stuff "tail -f stuff")
(defwindow some-window
:stacking "fg"
:monitor 12
:resizable true
:geometry (geometry :width "12%" :height "20px")
:reserve (struts :side "left" :distance "30px")
(bar :arg "bla"))
"#;
let mut files = YuckFiles::new();
let (span, asts) = files.load_str("config.yuck".to_string(), input.to_string()).unwrap();
let config = Config::generate(&mut files, asts);
insta::with_settings!({sort_maps => true}, {
insta::assert_ron_snapshot!(config.unwrap());
});
}