Improve CSS error handling by parsing GTK error output (see #446)
This commit is contained in:
parent
58907eac4b
commit
7623e7e692
13 changed files with 213 additions and 198 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -15,9 +15,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
version = "0.7.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -771,9 +771,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "grass"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34c6284b9a420f456146e1a10e602b82ea251f10b40854bcf04598b7121a3910"
|
||||
checksum = "bc5bedc3dbd71dcdd41900e1f58e4d431fa69dd67c04ae1f86ae1a0339edd849"
|
||||
dependencies = [
|
||||
"beef",
|
||||
"clap 2.34.0",
|
||||
|
@ -1856,9 +1856,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.26.1"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "621609553b14bca49448b3c97e625d7187980cc2a42fd169b4c3b306dcc4a7e9"
|
||||
checksum = "4ae2421f3e16b3afd4aa692d23b83d0ba42ee9b0081d5deeb7d21428d7195fb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
|
@ -2012,9 +2012,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c"
|
||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||
|
||||
[[package]]
|
||||
name = "unescape"
|
||||
|
|
|
@ -9,9 +9,11 @@ use crate::{
|
|||
*,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use eww_shared_util::VarName;
|
||||
use codespan_reporting::files::Files;
|
||||
use eww_shared_util::{Span, VarName};
|
||||
use glib::ObjectExt;
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use simplexpr::dynval::DynVal;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
|
@ -26,6 +28,8 @@ use yuck::{
|
|||
window_definition::WindowDefinition,
|
||||
window_geometry::{AnchorPoint, WindowGeometry},
|
||||
},
|
||||
error::DiagError,
|
||||
gen_diagnostic,
|
||||
value::Coords,
|
||||
};
|
||||
|
||||
|
@ -145,9 +149,15 @@ impl App {
|
|||
if let Err(e) = config_result.and_then(|new_config| self.load_config(new_config)) {
|
||||
errors.push(e)
|
||||
}
|
||||
let css_result = 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)) {
|
||||
errors.push(e)
|
||||
match crate::config::scss::parse_scss_from_file(&self.paths.get_eww_scss_path()) {
|
||||
Ok((file_id, css)) => {
|
||||
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)?;
|
||||
|
@ -400,11 +410,28 @@ impl App {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_css(&mut self, css: &str) -> Result<()> {
|
||||
self.css_provider.load_from_data(css.as_bytes())?;
|
||||
/// Load a given CSS string into the gtk css provider, returning a nicely formatted [`DiagError`] when GTK errors out
|
||||
pub fn load_css(&mut self, file_id: usize, css: &str) -> Result<()> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_window(
|
||||
monitor_geometry: gdk::Rectangle,
|
||||
|
|
|
@ -3,8 +3,8 @@ use eww_shared_util::VarName;
|
|||
use std::collections::HashMap;
|
||||
use yuck::{
|
||||
config::{
|
||||
file_provider::YuckFiles, script_var_definition::ScriptVarDefinition, validate::ValidationError,
|
||||
widget_definition::WidgetDefinition, window_definition::WindowDefinition, Config,
|
||||
script_var_definition::ScriptVarDefinition, validate::ValidationError, widget_definition::WidgetDefinition,
|
||||
window_definition::WindowDefinition, Config,
|
||||
},
|
||||
error::DiagError,
|
||||
format_diagnostic::ToDiagnostic,
|
||||
|
@ -12,7 +12,7 @@ use yuck::{
|
|||
|
||||
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;
|
||||
|
||||
|
@ -20,7 +20,7 @@ use super::script_var;
|
|||
/// resetting and applying the global YuckFiles object in [`crate::error_handling_ctx`].
|
||||
pub fn read_from_eww_paths(eww_paths: &EwwPaths) -> Result<EwwConfig> {
|
||||
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.
|
||||
|
@ -49,7 +49,7 @@ impl Default for EwwConfig {
|
|||
|
||||
impl EwwConfig {
|
||||
/// 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();
|
||||
if !yuck_path.exists() {
|
||||
bail!("The configuration file `{}` does not exist", yuck_path.display());
|
||||
|
|
|
@ -2,15 +2,20 @@ use std::path::Path;
|
|||
|
||||
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
|
||||
/// 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 scss_file_content =
|
||||
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 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))
|
||||
}
|
||||
|
|
|
@ -10,31 +10,23 @@ use codespan_reporting::{
|
|||
use eww_shared_util::Span;
|
||||
use once_cell::sync::Lazy;
|
||||
use simplexpr::{dynval::ConversionError, eval::EvalError};
|
||||
use yuck::{
|
||||
config::{file_provider::YuckFiles, validate::ValidationError},
|
||||
error::DiagError,
|
||||
format_diagnostic::ToDiagnostic,
|
||||
};
|
||||
use yuck::{config::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() {
|
||||
*YUCK_FILES.write().unwrap() = YuckFiles::new();
|
||||
*FILE_DATABASE.write().unwrap() = FileDatabase::new();
|
||||
}
|
||||
|
||||
pub fn print_error(err: anyhow::Error) {
|
||||
match anyhow_err_to_diagnostic(&err) {
|
||||
Some(diag) => match stringify_diagnostic(diag) {
|
||||
Ok(diag) => {
|
||||
eprintln!("{}", diag);
|
||||
}
|
||||
Err(_) => {
|
||||
log::error!("{:?}", err);
|
||||
}
|
||||
Ok(diag) => eprintln!("{}", diag),
|
||||
Err(_) => log::error!("{:?}", err),
|
||||
},
|
||||
None => {
|
||||
log::error!("{:?}", err);
|
||||
}
|
||||
None => log::error!("{:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +61,7 @@ pub fn stringify_diagnostic(mut diagnostic: codespan_reporting::diagnostic::Diag
|
|||
config.chars.note_bullet = '→';
|
||||
let mut buf = Vec::new();
|
||||
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)?;
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
|
131
crates/eww/src/file_database.rs
Normal file
131
crates/eww/src/file_database.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ mod config;
|
|||
mod daemon_response;
|
||||
mod display_backend;
|
||||
mod error_handling_ctx;
|
||||
mod file_database;
|
||||
mod geometry;
|
||||
mod ipc_server;
|
||||
mod opts;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
if let Ok(eww_css) = config::scss::parse_scss_from_file(&app.paths.get_eww_scss_path()) {
|
||||
app.load_css(&eww_css)?;
|
||||
if let Ok((file_id, css)) = config::scss::parse_scss_from_file(&app.paths.get_eww_scss_path()) {
|
||||
if let Err(e) = app.load_css(file_id, &css) {
|
||||
error_handling_ctx::print_error(e);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize all the handlers and tasks running asyncronously
|
||||
|
|
|
@ -23,6 +23,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
use yuck::{
|
||||
config::file_provider::YuckFileProvider,
|
||||
error::{DiagError, DiagResult},
|
||||
format_diagnostic::{span_to_secondary_label, DiagnosticExt},
|
||||
gen_diagnostic,
|
||||
|
@ -868,8 +869,8 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
|||
if !content.is_empty() {
|
||||
let content_widget_use: DiagResult<_> = try {
|
||||
let ast = {
|
||||
let mut yuck_files = error_handling_ctx::YUCK_FILES.write().unwrap();
|
||||
let (span, asts) = yuck_files.load_str("<literal-content>".to_string(), content)?;
|
||||
let mut yuck_files = error_handling_ctx::FILE_DATABASE.write().unwrap();
|
||||
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)) {
|
||||
yuck_files.unload(file_id);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use itertools::Itertools;
|
|||
use simplexpr::SimplExpr;
|
||||
|
||||
use super::{
|
||||
file_provider::{FilesError, YuckFiles},
|
||||
file_provider::{FilesError, YuckFileProvider},
|
||||
script_var_definition::ScriptVarDefinition,
|
||||
validate::ValidationError,
|
||||
var_definition::VarDefinition,
|
||||
|
@ -98,7 +98,7 @@ pub struct 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 {
|
||||
TopLevel::VarDefinition(x) => {
|
||||
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);
|
||||
}
|
||||
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! {
|
||||
msg = format!("Included file `{}` not found", include.path),
|
||||
label = include.path_span => "Included here",
|
||||
|
@ -142,7 +142,7 @@ impl Config {
|
|||
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 {
|
||||
widget_definitions: HashMap::new(),
|
||||
window_definitions: HashMap::new(),
|
||||
|
@ -155,8 +155,8 @@ impl Config {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn generate_from_main_file(files: &mut YuckFiles, path: impl AsRef<Path>) -> DiagResult<Self> {
|
||||
let (span, top_levels) = files.load_file(path.as_ref().to_path_buf()).map_err(|err| match err {
|
||||
pub fn generate_from_main_file(files: &mut impl YuckFileProvider, path: impl AsRef<Path>) -> DiagResult<Self> {
|
||||
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::DiagError(x) => x,
|
||||
})?;
|
||||
|
|
|
@ -17,120 +17,8 @@ pub enum FilesError {
|
|||
DiagError(#[from] DiagError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum YuckSource {
|
||||
File(std::path::PathBuf),
|
||||
Literal(String),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
pub trait YuckFileProvider {
|
||||
fn load_yuck_file(&mut self, path: std::path::PathBuf) -> Result<(Span, Vec<Ast>), FilesError>;
|
||||
fn load_yuck_str(&mut self, name: String, content: String) -> Result<(Span, Vec<Ast>), DiagError>;
|
||||
fn unload(&mut self, id: usize);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ pub mod config;
|
|||
pub mod file_provider;
|
||||
pub mod monitor;
|
||||
pub mod script_var_definition;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
pub mod validate;
|
||||
pub mod var_definition;
|
||||
pub mod widget_definition;
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
Loading…
Add table
Reference in a new issue