diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock index 062d7f62671251913aad66b659d0ebda110426fe..79912599051150568032984aec0e5c18cda8bf13 100644 --- a/src/Rust/Cargo.lock +++ b/src/Rust/Cargo.lock @@ -118,6 +118,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "cfg_aliases", +] + [[package]] name = "bstr" version = "1.10.0" @@ -160,16 +169,16 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.75", + "syn 2.0.77", "tempfile", "toml", ] [[package]] name = "cc" -version = "1.1.13" +version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b" dependencies = [ "shlex", ] @@ -180,6 +189,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "ciborium" version = "0.2.2" @@ -209,9 +224,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -219,9 +234,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -232,9 +247,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.20" +version = "4.5.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aedc27e53da9ff495f5da6f4325390e71f46f886022b618303042e8ccf4bcac" +checksum = "18d7f143a7e709cbe6c34853dcd3bb1370c7e1bb4d9e7310ca8cb40b490ae035" dependencies = [ "clap", ] @@ -248,7 +263,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -398,7 +413,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "unicode-xid", ] @@ -432,9 +447,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "globset" @@ -489,9 +504,9 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -678,9 +693,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -748,9 +763,9 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", @@ -782,29 +797,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap", "itoa", @@ -836,10 +851,11 @@ checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" [[package]] name = "smol_str" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "66eaf762c5af19db3108300515c8aa7a50efc90ff745f4c62288052ebf9fdd25" dependencies = [ + "borsh", "serde", ] @@ -862,9 +878,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -920,7 +936,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1148,7 +1164,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1204,9 +1220,7 @@ dependencies = [ name = "vvs_xdg" version = "0.5.0" dependencies = [ - "anyhow", "log", - "serde", "thiserror", "vvs_utils", ] @@ -1243,7 +1257,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -1265,7 +1279,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1469,5 +1483,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] diff --git a/src/Rust/vvs_cli/src/config.rs b/src/Rust/vvs_cli/src/config.rs index 5e51a9b215dc7e318907c40922bfdb19a660240d..25772ae6c976df7ded339d3161dccaa2ded71c4c 100644 --- a/src/Rust/vvs_cli/src/config.rs +++ b/src/Rust/vvs_cli/src/config.rs @@ -2,7 +2,6 @@ use crate::args::Args; use clap_complete::Shell; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use thiserror::Error; use vvs_utils::*; use vvs_xdg::XDGConfigFileSerde; @@ -40,7 +39,7 @@ pub struct Config { pub includes: Vec<PathBuf>, } -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum ConfigFileError { #[error("deserialization error: {0}")] Deserialize(toml::de::Error), diff --git a/src/Rust/vvs_cli/src/main.rs b/src/Rust/vvs_cli/src/main.rs index 5c723d6b164f2a26e881aa98dba1c9bbb34febc2..6898acb3c307f83cd855f24b3962c25347ccc97a 100644 --- a/src/Rust/vvs_cli/src/main.rs +++ b/src/Rust/vvs_cli/src/main.rs @@ -9,13 +9,13 @@ use vvs_cli::{ args::Args, config::{Config, ConfigFile}, }; -use vvs_lang::*; +use vvs_lang::{FrontendPipeline, SearchPath, SourceCode}; use vvs_runtime::Runtime; use vvs_xdg::{file::TempFile, XDGConfig, XDGConfigMergedSilent}; fn print_font(n: impl AsRef<Path>, f: &[u8]) -> Result<()> { let f = vvs_font::Font::try_from(f).with_context(|| format!("failed to parse font {}", n.as_ref().display()))?; - let ts = Vec::from_iter(f.font_types().into_iter().map(|ty| ty.to_string())); + let ts = Vec::from_iter(f.font_types().into_iter().map(Into::<String>::into)); println!("###"); println!("# Family Name: {}", f.name().context("failed to get the font name")?); println!("# Name(s): {}", f.family_names().join(", ")); @@ -62,10 +62,8 @@ fn main() -> Result<()> { &SearchPath::from_iter(includes) .with_script_folder(script.parent().context("expected the script to have a parent folder")?), ) - .options(options.as_ref()) - .context("failed to parse option file")? - .run() - .context("failed to parse script files")?; + .options(options.as_ref())? + .run()?; if info { match ass_file { diff --git a/src/Rust/vvs_font/src/font.rs b/src/Rust/vvs_font/src/font.rs index 996ab5ed999cd9d220fd44a74e0f96b834f6e04d..fe18a2ee8277b54d5b589a6b463a7e2d4cb67cba 100644 --- a/src/Rust/vvs_font/src/font.rs +++ b/src/Rust/vvs_font/src/font.rs @@ -16,6 +16,12 @@ pub enum FontType { #[display("variable")] Variable, } +impl From<FontType> for String { + fn from(value: FontType) -> Self { + value.to_string() + } +} + /// Struct used to store informations we need about a font. #[derive(Debug)] pub struct Font<'a> { diff --git a/src/Rust/vvs_xdg/Cargo.toml b/src/Rust/vvs_xdg/Cargo.toml index 4389a7c498fb87731fdf1bde21f557f538eac42b..4063b04dc630548a7c7d87de6ed67e2b4ed6adb7 100644 --- a/src/Rust/vvs_xdg/Cargo.toml +++ b/src/Rust/vvs_xdg/Cargo.toml @@ -9,6 +9,4 @@ license.workspace = true [dependencies] vvs_utils.workspace = true thiserror.workspace = true -anyhow.workspace = true -serde.workspace = true log.workspace = true diff --git a/src/Rust/vvs_xdg/src/config.rs b/src/Rust/vvs_xdg/src/config.rs index b48c6f60fd4f138b2c77ff448f2d1d01bc6a4e1c..c165222158b7227363055f4148b7e86870ca5fe2 100644 --- a/src/Rust/vvs_xdg/src/config.rs +++ b/src/Rust/vvs_xdg/src/config.rs @@ -6,17 +6,20 @@ use std::{fs, marker}; /// Search configurations in all config folders and merge them. #[derive(Debug)] +#[non_exhaustive] pub struct XDGConfigMerged; /// Only get the config file with higher priority, which means searching in order: /// 1. $XDG_CONFIG_HOME <- which has a default value. /// 2. $XDG_CONFIG_DIRS <- which has a default value. #[derive(Debug)] +#[non_exhaustive] pub struct XDGConfigFirst; /// Search configurations in all config folders and merge them. If the parsing for a configuration /// file failed then log it and continue execution like normal, i.e. fail silently. #[derive(Debug)] +#[non_exhaustive] pub struct XDGConfigMergedSilent; /// We will write one impl block for merged searches. @@ -31,10 +34,12 @@ mod private_merged { impl Sealed for XDGConfigMergedSilent { fn try_read<Format: XDGConfigFileSerde>(config: &XDGConfig<Format, Self>) -> Result<Format, XDGError> { let mut result = config.search_files()?.into_iter().filter_map(|file| { - let file = Result::ok(fs::read_to_string(file) - .map_err(|err| log::error!(target: "xdg", "{}", XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), err))))?; - Result::ok(Format::deserialize(&file) - .map_err(|e| log::error!(target: "xdg", "deserialize error for {} on file {}: {e}", config.app, config.get_file()))) + let file = fs::read_to_string(file) + .map_err(|e| log::error!(target: "xdg", "{}", XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), e))) + .ok()?; + Format::deserialize(&file) + .map_err(|e| log::error!(target: "xdg", "deserialize error for {} on file {}: {e}", config.app, config.get_file())) + .ok() }); let mut first = result @@ -50,12 +55,12 @@ mod private_merged { let mut result = Iterator::collect::<Result<Vec<_>, _>>(config.search_files()?.into_iter().map(|file| { let file = fs::read_to_string(file) .map_err(|err| XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), err))?; - let file = Format::deserialize(&file).map_err(|err| { + Format::deserialize(&file).map_err(|err| { log::error!(target: "xdg", "deserialize error for application {} on file {}: {err}", config.app, config.get_file()); XDGError::DeserializeError(config.app.to_string(), config.get_file().to_string()) - })?; - Ok(file) + }) }))?.into_iter(); + let mut first = result .next() .ok_or_else(|| XDGError::ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; @@ -63,6 +68,22 @@ mod private_merged { Ok(first) } } + + impl Sealed for XDGConfigFirst { + fn try_read<Format: XDGConfigFileSerde>(config: &XDGConfig<Format, Self>) -> Result<Format, XDGError> { + let file = config + .search_files()? + .into_first() + .ok_or_else(|| XDGError::ConfigNotFound(config.app.to_string(), config.get_file().to_string()))?; + let file = fs::read_to_string(file) + .map_err(|err| XDGError::ConfigIO(config.app.to_string(), config.get_file().to_string(), err))?; + Format::deserialize(&file) + .map_err(|err| { + log::error!(target: "xdg", "deserialize error on application {} on file {}: {err}", config.app, config.get_file()); + XDGError::DeserializeError(config.app.to_string(), config.get_file().to_string()) + }) + } + } } /// A struct to contain informations about the app we are querying the config for. The behaviour of @@ -81,7 +102,7 @@ mod private_merged { /// then all the found files are parsed and then merged, an error is returned on the first /// failure of the deserialization process. #[derive(Debug)] -pub struct XDGConfig<'a, Format: XDGConfigFileSerde, Merged> { +pub struct XDGConfig<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> { /// The application name. app: &'a str, @@ -95,7 +116,7 @@ pub struct XDGConfig<'a, Format: XDGConfigFileSerde, Merged> { _merged: marker::PhantomData<Merged>, } -impl<'a, Format: XDGConfigFileSerde, Merged> XDGConfig<'a, Format, Merged> { +impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// By default we say that the default config file is ${FOLDER}/${APP}/config like many /// applications do. const DEFAULT_FILE: &'static str = "config"; @@ -103,14 +124,13 @@ impl<'a, Format: XDGConfigFileSerde, Merged> XDGConfig<'a, Format, Merged> { /// Create a new [XDGConfig] helper. #[inline] pub fn new(app: &'a str) -> Self { - Self { app, file: Default::default(), _type: Default::default(), _merged: Default::default() } + Self { app, file: Default::default(), _type: marker::PhantomData, _merged: marker::PhantomData } } /// Change the file to resolve. #[inline] - pub fn file(&mut self, file: &'a str) -> &mut Self { - self.file = Some(file); - self + pub fn file(self, file: &'a str) -> Self { + Self { file: Some(file), ..self } } /// Get the name of the config file that we will try to read. Returns the default if not set by @@ -134,102 +154,7 @@ impl<'a, Format: XDGConfigFileSerde, Merged> XDGConfig<'a, Format, Merged> { pub fn prepare_folder(&self) -> impl IntoIterator<Item = <MaybeFolderList as IntoIterator>::Item> { XDGFolder::ConfigDirs.prepare_folder() } -} - -impl<'a, Format: XDGConfigFileSerde> XDGConfig<'a, Format, XDGConfigFirst> { - /// Try to read the config file and deserialize it. - pub fn try_read(&self) -> Result<Format, XDGError> { - let Some(file) = self.search_files()?.into_first() else { - return Err(XDGError::ConfigNotFound(self.app.to_string(), self.get_file().to_string())); - }; - let file = fs::read_to_string(file) - .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err))?; - Format::deserialize(&file) - .map_err(|err| { - log::error!(target: "xdg", "deserialize error on application {} on file {}: {err}", self.app, self.get_file()); - XDGError::DeserializeError(self.app.to_string(), self.get_file().to_string()) - }) - } -} - -impl<'a, Format: XDGConfigFileSerde> XDGConfig<'a, Format, XDGConfigFirst> { - /// Try to read the config file and deserialize it. If an error is encountred at any point, log - /// it and return the provided default. If needed the default value is written on the disk, - /// note that this operation may fail silently. - #[inline] - pub fn read_or(&self, default: Format) -> Format { - self.try_read().unwrap_or_else(|err| { - log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(default) - }) - } - - /// Try to read the config file and deserialize it. If an error is encountred at any point, log - /// it and return the provided default. If needed the default value is written on the disk, - /// note that this operation may fail silently. - #[inline] - pub fn read_or_else(&self, cb: impl FnOnce() -> Format) -> Format { - self.try_read().unwrap_or_else(|err| { - log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(cb()) - }) - } - - /// Write a value to the default config file location, the one with the higher priority - /// (usually the user's one) - fn write(&self, value: &Format) -> Result<(), XDGError> { - let content = Format::serialize(value) - .map_err(|err| { - log::error!(target: "xdg", "serialize error for application {} on file {}: {err}", self.app, self.get_file()); - XDGError::SerializeError(self.app.to_string(), self.get_file().to_string()) - })?; - - let path = XDGFolder::ConfigDirs - .find(self.app, self.get_file(), XDGFindBehaviour::FirstOrCreate)? - .into_first() - .expect("the user must have at least one location to place a config file, permission or fs quota problem?"); - - match fs::create_dir_all( - path.parent() - .ok_or(XDGError::ConfigFileHasNoParentFolder(self.app.to_string(), self.get_file().to_string()))?, - ) { - Err(err) if !matches!(err.kind(), std::io::ErrorKind::AlreadyExists) => { - return Err(XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err)) - } - _ => {} - } - - fs::write(path, content) - .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err)) - } - - /// Same as [XDGConfig::write] but log any error and fail silently, returning the value that - /// was attempted to be written to disk. - #[inline] - fn write_silent(&self, value: Format) -> Format { - if let Err(err) = self.write(&value) { - log::error!(target: "xdg", "failed to write default with err: {err}") - } - value - } -} -impl<'a, Format: XDGConfigFileSerde + Default> XDGConfig<'a, Format, XDGConfigFirst> { - /// Try to read the config file and deserialize it. If an error is encountred at any point, log - /// it and return the default. If needed the default value is written on the disk, note that - /// this operation may fail silently. - #[inline] - pub fn read_or_default(&self) -> Format { - self.try_read().unwrap_or_else(|err| { - log::error!(target: "xdg", "read error, return default value to user: {err}"); - self.write_silent(Default::default()) - }) - } -} - -// XDGConfigMerged + XDGConfigMergedSilent - -impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// When trying to read or write the default, we write the file with the same logic as the /// [XDGConfigFirst] variant, we add this function to reduce the code to write for the write /// logic... @@ -248,9 +173,7 @@ impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<' pub fn try_read(&self) -> Result<Format, XDGError> { Merged::try_read(self) } -} -impl<'a, Format: XDGConfigFileSerde, Merged: private_merged::Sealed> XDGConfig<'a, Format, Merged> { /// Try to read the config files and deserialize them. If an error is encountred at any point, /// log it and return the provided default if the merge was not silent, skip the file if it was /// silent. If needed the default value is written on the disk, note that this operation may @@ -289,3 +212,43 @@ impl<'a, Format: XDGConfigFileSerde + Default, Merged: private_merged::Sealed> X }) } } + +impl<'a, Format: XDGConfigFileSerde> XDGConfig<'a, Format, XDGConfigFirst> { + /// Write a value to the default config file location, the one with the higher priority + /// (usually the user's one) + fn write(&self, value: &Format) -> Result<(), XDGError> { + let content = Format::serialize(value) + .map_err(|err| { + log::error!(target: "xdg", "serialize error for application {} on file {}: {err}", self.app, self.get_file()); + XDGError::SerializeError(self.app.to_string(), self.get_file().to_string()) + })?; + + let path = XDGFolder::ConfigDirs + .find(self.app, self.get_file(), XDGFindBehaviour::FirstOrCreate)? + .into_first() + .expect("the user must have at least one location to place a config file, permission or fs quota problem?"); + + match fs::create_dir_all( + path.parent() + .ok_or(XDGError::ConfigFileHasNoParentFolder(self.app.to_string(), self.get_file().to_string()))?, + ) { + Err(err) if !matches!(err.kind(), std::io::ErrorKind::AlreadyExists) => { + return Err(XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err)) + } + _ => {} + } + + fs::write(path, content) + .map_err(|err| XDGError::ConfigIO(self.app.to_string(), self.get_file().to_string(), err)) + } + + /// Same as [XDGConfig::write] but log any error and fail silently, returning the value that + /// was attempted to be written to disk. + #[inline] + fn write_silent(&self, value: Format) -> Format { + if let Err(err) = self.write(&value) { + log::error!(target: "xdg", "failed to write default with err: {err}") + } + value + } +} diff --git a/src/Rust/vvs_xdg/src/file/lock.rs b/src/Rust/vvs_xdg/src/file/lock.rs index 54f80997df672b237845c0cb114d35dda4539b6b..992adb65c43e699e1625332630ded232ec566e86 100644 --- a/src/Rust/vvs_xdg/src/file/lock.rs +++ b/src/Rust/vvs_xdg/src/file/lock.rs @@ -3,16 +3,15 @@ use std::{ collections::hash_map::DefaultHasher, fs, hash::{Hash, Hasher}, - io::Error as IoError, + io::{Error as IoError, ErrorKind as IoErrorKind}, path::Path, }; -use thiserror::Error; /// An error can be comming from the IO thing or the resource could be already locked. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum LockError { #[error("{0}")] - IO(std::io::Error), + IO(#[from] IoError), #[error("resource is already locked")] AlreadyLocked, @@ -41,7 +40,7 @@ impl LockFile { #[inline] fn handle_io_error(err: IoError) -> LockError { match err.kind() { - std::io::ErrorKind::AlreadyExists => LockError::AlreadyLocked, + IoErrorKind::AlreadyExists => LockError::AlreadyLocked, _ => LockError::IO(err), } } diff --git a/src/Rust/vvs_xdg/src/file/temp.rs b/src/Rust/vvs_xdg/src/file/temp.rs index 514d496f46fe1ed276620defc0cd6b9bc3d56426..3481602447d8604596b53e199482b4f165a3884d 100644 --- a/src/Rust/vvs_xdg/src/file/temp.rs +++ b/src/Rust/vvs_xdg/src/file/temp.rs @@ -1,18 +1,17 @@ use crate::*; use std::{ fs::{self, File, OpenOptions}, - io::Error as IoError, + io::{Error as IoError, ErrorKind as IoErrorKind}, ops::{Deref, DerefMut}, path::{Path, PathBuf}, }; -use thiserror::Error; use vvs_utils::rand; /// An error can be comming from the IO thing or we have a collision with the temp file name. -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum TempError { #[error("{0}")] - IO(#[from] std::io::Error), + IO(#[from] IoError), #[error("generated a duplicate temporary file")] Duplicate, @@ -74,7 +73,7 @@ impl TempFile { #[inline] fn handle_io_error(err: IoError) -> TempError { match err.kind() { - std::io::ErrorKind::AlreadyExists => TempError::Duplicate, + IoErrorKind::AlreadyExists => TempError::Duplicate, _ => TempError::IO(err), } } @@ -93,7 +92,7 @@ impl TempFile { #[cfg(not(unix))] fn create(app: impl AsRef<str>, file: impl AsRef<str>) -> TempResult<PathBuf> { - use std::{env, io::ErrorKind as IoErrorKind}; + use std::env; let folder = env::var("TEMP") .map(PathBuf::from) .or_else(|_| { diff --git a/src/Rust/vvs_xdg/src/folders.rs b/src/Rust/vvs_xdg/src/folders.rs index 4f7364b9d77b92d0469c402e0bd48217590b412b..e3afb9b2a8a8ae65c095ddaf83b382f306e4410e 100644 --- a/src/Rust/vvs_xdg/src/folders.rs +++ b/src/Rust/vvs_xdg/src/folders.rs @@ -125,18 +125,17 @@ impl XDGFolder { } } - let home = crate::home_folder(); match std::env::var(self.env_var_name()) { Ok(folder_list) if self.is_list() => MaybeFolderList::from_str_list(&folder_list, Self::SEPARATOR), Ok(folder) => MaybeFolderList::Folder(PathBuf::from(folder)), Err(_) => match self { - XDGFolder::DataHome => MaybeFolderList::Folder(home.join(".local/share")), - XDGFolder::ConfigHome => MaybeFolderList::Folder(home.join(".config")), - XDGFolder::StateHome => MaybeFolderList::Folder(home.join(".local/state")), - XDGFolder::CacheHome => MaybeFolderList::Folder(home.join(".cache")), + XDGFolder::DataHome => MaybeFolderList::Folder(crate::home_folder().join(".local/share")), + XDGFolder::ConfigHome => MaybeFolderList::Folder(crate::home_folder().join(".config")), + XDGFolder::StateHome => MaybeFolderList::Folder(crate::home_folder().join(".local/state")), + XDGFolder::CacheHome => MaybeFolderList::Folder(crate::home_folder().join(".cache")), XDGFolder::RuntimeDir => panic!("failed to find the env variable $XDG_RUNTIME_DIR"), - XDGFolder::DataDirs => variables::data_dirs(&home), - XDGFolder::ConfigDirs => variables::config_dirs(&home), + XDGFolder::DataDirs => variables::data_dirs(crate::home_folder()), + XDGFolder::ConfigDirs => variables::config_dirs(crate::home_folder()), }, } } @@ -144,15 +143,12 @@ impl XDGFolder { /// Get the folders list of what you asked for, but try to create them and only return existing /// ones. The thing returned is a thing that can be iterate over to facilitate chaining. pub fn prepare_folder(&self) -> impl IntoIterator<Item = <MaybeFolderList as IntoIterator>::Item> { - self.get_folder().into_iter().filter_map(|folder| { - if folder.is_dir() { - Some(folder) - } else { - std::fs::create_dir_all(&folder) - .map(|()| folder) - .map_err(|err| log::error!(target: "xdg", "{err}")) - .ok() - } + self.get_folder().into_iter().filter_map(|dir| match dir.is_dir() { + true => Some(dir), + false => std::fs::create_dir_all(&dir) + .map(|_| dir) + .map_err(|e| log::error!(target: "xdg", "{e}")) + .ok(), }) } @@ -207,26 +203,28 @@ impl XDGFolder { let (mut exists, mut can_create): (MaybeFolderList, MaybeFolderList) = matches.iter().partition(|m| matches!(m, FindState::Exists(_))); - use XDGFindBehaviour::*; match (exists.is_some(), can_create.is_some()) { // [XDGFindBehaviour::FirstOrCreate] case... - (true, false) if opt == FirstOrCreate => Ok(exists.into_first().into_iter().collect()), - (false, true) if opt == FirstOrCreate => Ok(can_create.into_first().into_iter().collect()), + (true, false) if opt == XDGFindBehaviour::FirstOrCreate => Ok(exists.into_first().into_iter().collect()), + (false, true) if opt == XDGFindBehaviour::FirstOrCreate => { + Ok(can_create.into_first().into_iter().collect()) + } // Should we return the one list that is not empty? - (true, false) if matches!(opt, AllFiles | ExistingOnly) => Ok(exists), - (false, true) if matches!(opt, AllFiles | NonExistingOnly) => Ok(can_create), + (true, false) if matches!(opt, XDGFindBehaviour::AllFiles | XDGFindBehaviour::ExistingOnly) => Ok(exists), + (false, true) if matches!(opt, XDGFindBehaviour::AllFiles | XDGFindBehaviour::NonExistingOnly) => { + Ok(can_create) + } // Complicated case (true, true) => match opt { - AllFiles => { + XDGFindBehaviour::AllFiles => { exists.append(&mut can_create); Ok(exists) } - - FirstOrCreate => Ok(exists.into_first().into_iter().collect()), - ExistingOnly => Ok(exists), - NonExistingOnly => Ok(can_create), + XDGFindBehaviour::FirstOrCreate => Ok(exists.into_first().into_iter().collect()), + XDGFindBehaviour::ExistingOnly => Ok(exists), + XDGFindBehaviour::NonExistingOnly => Ok(can_create), }, // The list that we want is empty, or all lists are empty, returns nothing -> should it @@ -238,8 +236,7 @@ impl XDGFolder { /// Is this variable a folder list? #[inline] pub fn is_list(&self) -> bool { - use XDGFolder::*; - matches!(self, DataDirs | ConfigDirs) + matches!(self, XDGFolder::DataDirs | XDGFolder::ConfigDirs) } /// Get the env variable name associated with the folder or folder list. diff --git a/src/Rust/vvs_xdg/src/lib.rs b/src/Rust/vvs_xdg/src/lib.rs index 9183ddf8c07823a4421401da48a6618b58f587ae..cbfc9bce294f875425df0415bbc4ece7a3ed79ad 100644 --- a/src/Rust/vvs_xdg/src/lib.rs +++ b/src/Rust/vvs_xdg/src/lib.rs @@ -8,14 +8,11 @@ mod tests; mod config; mod folders; -mod options; mod paths; -mod traits; -pub use self::{config::*, folders::*, options::*, paths::*, traits::*}; +pub use self::{config::*, folders::*, paths::*}; use std::{io::Error as IoError, path::PathBuf}; -use thiserror::Error; /// Get the user's home folder. Panics if the env variable was not found or it can't be /// canonicalized. @@ -26,7 +23,7 @@ pub fn home_folder() -> PathBuf { .expect("failed to canonicalize the $HOME folder path") } -#[derive(Debug, Error)] +#[derive(Debug, thiserror::Error)] pub enum XDGError { #[error("failed to find file {2} for application {1} with folder family {0}")] NotFound(XDGFolder, String, String), @@ -46,3 +43,38 @@ pub enum XDGError { #[error("serialization failed on file {1} for application {0}")] SerializeError(String, String), } + +/// Control the behaviour of the [XDGFolder::find] function on conflicts. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum XDGFindBehaviour { + /// Returns only the files that already exists. If no file is found, returns the file with the + /// post priority that can be created, usually where the user want this file to exists. If you + /// don't want to merge config/data files/folders this can be a sane default. + #[default] + FirstOrCreate, + + /// Returns only the files that already exists. If you want to merge config/data files/folders + /// this can be a sane default. + ExistingOnly, + + /// Only returns files that don't already exists and may be created. Note that even if a file + /// can be created by the user, it won't necessarily means that you will be able to, keep in + /// mind that race conditions may occur here. + NonExistingOnly, + + /// Returns all files, the ones that exist and the ones that may be created. The user will need + /// to handle the returned files as he sees fit. + AllFiles, +} + +/// We force the user to choose a way to serialize and deserialize the config file. This way, we +/// are format agnostique here and don't even depend on serde. +pub trait XDGConfigFileSerde: Sized + Extend<Self> { + type Error: std::error::Error; + + /// Read the content of the config file. + fn deserialize(input: &str) -> Result<Self, Self::Error>; + + /// Write the config file to string, so that we can write it latter into a file. + fn serialize(&self) -> Result<String, Self::Error>; +} diff --git a/src/Rust/vvs_xdg/src/options.rs b/src/Rust/vvs_xdg/src/options.rs deleted file mode 100644 index 3985de17e9e1be548a0cb8cecb9a8503eb473422..0000000000000000000000000000000000000000 --- a/src/Rust/vvs_xdg/src/options.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Options that can be passed to some functions to control the behaviour. - -/// Control the behaviour of the [XDGFolder::find] function on conflicts. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub enum XDGFindBehaviour { - /// Returns only the files that already exists. If no file is found, returns the file with the - /// post priority that can be created, usually where the user want this file to exists. If you - /// don't want to merge config/data files/folders this can be a sane default. - #[default] - FirstOrCreate, - - /// Returns only the files that already exists. If you want to merge config/data files/folders - /// this can be a sane default. - ExistingOnly, - - /// Only returns files that don't already exists and may be created. Note that even if a file - /// can be created by the user, it won't necessarily means that you will be able to, keep in - /// mind that race conditions may occur here. - NonExistingOnly, - - /// Returns all files, the ones that exist and the ones that may be created. The user will need - /// to handle the returned files as he sees fit. - AllFiles, -} diff --git a/src/Rust/vvs_xdg/src/paths.rs b/src/Rust/vvs_xdg/src/paths.rs index 7eec911df4add0c483c191671f06ac92f02fc4bb..326348c0632db12e42a698c5cb94b0c8744de26d 100644 --- a/src/Rust/vvs_xdg/src/paths.rs +++ b/src/Rust/vvs_xdg/src/paths.rs @@ -42,57 +42,51 @@ impl MaybeFolderList { /// Get the first folder, or the last folder. If no folder is present just returns [None]. #[inline] pub fn first(&self) -> Option<&Path> { - use MaybeFolderList::*; match self { - Empty => None, - Folder(f) | Many(f, _) => Some(f.as_ref()), + MaybeFolderList::Empty => None, + MaybeFolderList::Folder(f) | MaybeFolderList::Many(f, _) => Some(f.as_ref()), } } /// Append another [MaybeFolderList] at the end of a [MaybeFolderList]. Empties the other /// variable and try to reuse memory. pub fn append(&mut self, other: &mut MaybeFolderList) { - use MaybeFolderList::*; - match (self, other) { - (this @ Empty, other) => match other { - Empty => {} - Folder(f1) => { - *this = Folder(mem::take(f1)); - *other = Empty; - } - Many(f1, fs) => { - *this = Many(mem::take(f1), mem::take(fs)); - *other = Empty; - } + (this @ MaybeFolderList::Empty, other) => match mem::replace(other, MaybeFolderList::Empty) { + MaybeFolderList::Empty => {} + MaybeFolderList::Folder(f1) => *this = MaybeFolderList::Folder(f1), + MaybeFolderList::Many(f1, fs) => *this = MaybeFolderList::Many(f1, fs), }, - (_, Empty) => {} + (_, MaybeFolderList::Empty) => {} - (Many(_, fs), other @ Folder(_)) => { - let Folder(f2) = other else { unreachable!() }; - fs.push(mem::take(f2)); - *other = Empty; + (MaybeFolderList::Many(_, fs), other @ MaybeFolderList::Folder(_)) => { + let MaybeFolderList::Folder(f2) = mem::replace(other, MaybeFolderList::Empty) else { + unreachable!() + }; + fs.push(f2); } - (Many(_, fs), other @ Many(_, _)) => { - let Many(f2, ref mut fs2) = other else { unreachable!() }; + (MaybeFolderList::Many(_, fs), other @ MaybeFolderList::Many(..)) => { + let MaybeFolderList::Many(f2, ref mut fs2) = mem::replace(other, MaybeFolderList::Empty) else { + unreachable!() + }; fs.reserve(fs2.len() + 1); - fs.push(mem::take(f2)); + fs.push(f2); fs.append(fs2); - *other = Empty; } - (this @ Folder(_), other) => { - let Folder(f1) = this else { unreachable!() }; - match other { - Empty => unreachable!(), - Folder(f2) => *this = Many(mem::take(f1), vec![mem::take(f2)]), - Many(f2, ref mut fs) => { - fs.insert(0, mem::take(f2)); - *this = Many(mem::take(f1), mem::take(fs)); + (this @ MaybeFolderList::Folder(_), other) => { + let MaybeFolderList::Folder(f1) = mem::take(this) else { + unreachable!() + }; + match mem::replace(other, MaybeFolderList::Empty) { + MaybeFolderList::Empty => unreachable!(), + MaybeFolderList::Folder(f2) => *this = MaybeFolderList::Many(f1, vec![f2]), + MaybeFolderList::Many(f2, mut fs) => { + fs.insert(0, f2); + *this = MaybeFolderList::Many(f1, fs); } } - *other = Empty; } } } @@ -102,10 +96,9 @@ impl MaybeFolderList { /// [MaybeFolderList]. #[inline] pub fn into_first(self) -> Option<PathBuf> { - use MaybeFolderList::*; match self { - Empty => None, - Folder(f) | Many(f, _) => Some(f), + MaybeFolderList::Empty => None, + MaybeFolderList::Folder(f) | MaybeFolderList::Many(f, _) => Some(f), } } @@ -133,28 +126,26 @@ impl MaybeFolderList { impl std::iter::Extend<PathBuf> for MaybeFolderList { fn extend<T: IntoIterator<Item = PathBuf>>(&mut self, iter: T) { - use MaybeFolderList::*; - let mut iter = iter.into_iter(); match self { - Empty => { + MaybeFolderList::Empty => { + let mut iter = iter.into_iter(); if let Some(next) = iter.next() { - *self = Many(next, iter.collect()); + *self = MaybeFolderList::Many(next, iter.collect()); } } - Folder(f1) => *self = Many(mem::take(f1), iter.collect()), - Many(_, fs) => fs.extend(iter), + MaybeFolderList::Folder(f1) => *self = MaybeFolderList::Many(mem::take(f1), iter.into_iter().collect()), + MaybeFolderList::Many(_, fs) => fs.extend(iter), } } } impl<'a> std::iter::Extend<&'a MaybeFolderList> for MaybeFolderList { fn extend<T: IntoIterator<Item = &'a MaybeFolderList>>(&mut self, iter: T) { - use MaybeFolderList::*; - let other = iter.into_iter().flat_map(|iter| iter.iter()); + let other = iter.into_iter().flat_map(MaybeFolderList::iter); match self { - Empty => *self = other.collect(), - Folder(f1) => *self = Many(mem::take(f1), other.cloned().collect()), - Many(_, ref mut fs) => fs.extend(other.cloned()), + MaybeFolderList::Empty => *self = other.collect(), + MaybeFolderList::Folder(f1) => *self = MaybeFolderList::Many(mem::take(f1), other.cloned().collect()), + MaybeFolderList::Many(_, ref mut fs) => fs.extend(other.cloned()), } } } diff --git a/src/Rust/vvs_xdg/src/traits.rs b/src/Rust/vvs_xdg/src/traits.rs deleted file mode 100644 index 728ef7cf43135766c273e64a620b3c3a8e47c269..0000000000000000000000000000000000000000 --- a/src/Rust/vvs_xdg/src/traits.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub trait XDGConfigFileSerde: Sized + Extend<Self> { - type Error: std::error::Error; - - fn deserialize(input: &str) -> Result<Self, Self::Error>; - - fn serialize(&self) -> Result<String, Self::Error>; -}