diff options
Diffstat (limited to 'alacritty/src/config/mod.rs')
-rw-r--r-- | alacritty/src/config/mod.rs | 202 |
1 files changed, 131 insertions, 71 deletions
diff --git a/alacritty/src/config/mod.rs b/alacritty/src/config/mod.rs index 2f230b06..821e9b6b 100644 --- a/alacritty/src/config/mod.rs +++ b/alacritty/src/config/mod.rs @@ -1,11 +1,14 @@ use std::fmt::{self, Display, Formatter}; use std::path::{Path, PathBuf}; +use std::result::Result as StdResult; use std::{env, fs, io}; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use serde::Deserialize; -use serde_yaml::mapping::Mapping; -use serde_yaml::Value; +use serde_yaml::Error as YamlError; +use toml::de::Error as TomlError; +use toml::ser::Error as TomlSeError; +use toml::{Table, Value}; use alacritty_terminal::config::LOG_TARGET_CONFIG; @@ -30,7 +33,7 @@ pub use crate::config::mouse::{ClickHandler, Mouse}; pub use crate::config::ui_config::UiConfig; /// Maximum number of depth for the configuration file imports. -const IMPORT_RECURSION_LIMIT: usize = 5; +pub const IMPORT_RECURSION_LIMIT: usize = 5; /// Result from config loading. pub type Result<T> = std::result::Result<T, Error>; @@ -47,8 +50,14 @@ pub enum Error { /// io error reading file. Io(io::Error), - /// Not valid yaml or missing parameters. - Yaml(serde_yaml::Error), + /// Invalid toml. + Toml(TomlError), + + /// Failed toml serialization. + TomlSe(TomlSeError), + + /// Invalid yaml. + Yaml(YamlError), } impl std::error::Error for Error { @@ -57,6 +66,8 @@ impl std::error::Error for Error { Error::NotFound => None, Error::ReadingEnvHome(err) => err.source(), Error::Io(err) => err.source(), + Error::Toml(err) => err.source(), + Error::TomlSe(err) => err.source(), Error::Yaml(err) => err.source(), } } @@ -70,6 +81,8 @@ impl Display for Error { write!(f, "Unable to read $HOME environment variable: {}", err) }, Error::Io(err) => write!(f, "Error reading config file: {}", err), + Error::Toml(err) => write!(f, "Config error: {}", err), + Error::TomlSe(err) => write!(f, "Yaml conversion error: {}", err), Error::Yaml(err) => write!(f, "Config error: {}", err), } } @@ -91,16 +104,32 @@ impl From<io::Error> for Error { } } -impl From<serde_yaml::Error> for Error { - fn from(val: serde_yaml::Error) -> Self { +impl From<TomlError> for Error { + fn from(val: TomlError) -> Self { + Error::Toml(val) + } +} + +impl From<TomlSeError> for Error { + fn from(val: TomlSeError) -> Self { + Error::TomlSe(val) + } +} + +impl From<YamlError> for Error { + fn from(val: YamlError) -> Self { Error::Yaml(val) } } /// Load the configuration file. pub fn load(options: &Options) -> UiConfig { - let config_options = options.config_options.clone(); - let config_path = options.config_file.clone().or_else(installed_config); + let config_options = options.config_options.0.clone(); + let config_path = options + .config_file + .clone() + .or_else(|| installed_config("toml")) + .or_else(|| installed_config("yml")); // Load the config using the following fallback behavior: // - Config path + CLI overrides @@ -128,7 +157,7 @@ pub fn reload(config_path: &Path, options: &Options) -> Result<UiConfig> { debug!("Reloading configuration file: {:?}", config_path); // Load config, propagating errors. - let config_options = options.config_options.clone(); + let config_options = options.config_options.0.clone(); let mut config = load_from(config_path, config_options)?; after_loading(&mut config, options); @@ -179,6 +208,16 @@ fn parse_config( ) -> Result<Value> { config_paths.push(path.to_owned()); + // Deserialize the configuration file. + let config = deserialize_config(path)?; + + // Merge config with imports. + let imports = load_imports(&config, config_paths, recursion_limit); + Ok(serde_utils::merge(imports, config)) +} + +/// Deserialize a configuration file. +pub fn deserialize_config(path: &Path) -> Result<Value> { let mut contents = fs::read_to_string(path)?; // Remove UTF-8 BOM. @@ -186,51 +225,84 @@ fn parse_config( contents = contents.split_off(3); } + // Convert YAML to TOML as a transitionary fallback mechanism. + let extension = path.extension().unwrap_or_default(); + if (extension == "yaml" || extension == "yml") && !contents.trim().is_empty() { + warn!("YAML config {path:?} is deprecated, please migrate to TOML"); + + let value: serde_yaml::Value = serde_yaml::from_str(&contents)?; + contents = toml::to_string(&value)?; + } + // Load configuration file as Value. - let config: Value = match serde_yaml::from_str(&contents) { - Ok(config) => config, - Err(error) => { - // Prevent parsing error with an empty string and commented out file. - if error.to_string() == "EOF while parsing a value" { - Value::Mapping(Mapping::new()) - } else { - return Err(Error::Yaml(error)); - } - }, - }; + let config: Value = toml::from_str(&contents)?; - // Merge config with imports. - let imports = load_imports(&config, config_paths, recursion_limit); - Ok(serde_utils::merge(imports, config)) + Ok(config) } /// Load all referenced configuration files. fn load_imports(config: &Value, config_paths: &mut Vec<PathBuf>, recursion_limit: usize) -> Value { - let imports = match config.get("import") { - Some(Value::Sequence(imports)) => imports, - Some(_) => { - error!(target: LOG_TARGET_CONFIG, "Invalid import type: expected a sequence"); - return Value::Null; + // Get paths for all imports. + let import_paths = match imports(config, recursion_limit) { + Ok(import_paths) => import_paths, + Err(err) => { + error!(target: LOG_TARGET_CONFIG, "{err}"); + return Value::Table(Table::new()); }, - None => return Value::Null, + }; + + // Parse configs for all imports recursively. + let mut merged = Value::Table(Table::new()); + for import_path in import_paths { + let path = match import_path { + Ok(path) => path, + Err(err) => { + error!(target: LOG_TARGET_CONFIG, "{err}"); + continue; + }, + }; + + if !path.exists() { + info!(target: LOG_TARGET_CONFIG, "Config import not found:\n {:?}", path.display()); + continue; + } + + match parse_config(&path, config_paths, recursion_limit - 1) { + Ok(config) => merged = serde_utils::merge(merged, config), + Err(err) => { + error!(target: LOG_TARGET_CONFIG, "Unable to import config {:?}: {}", path, err) + }, + } + } + + merged +} + +// TODO: Merge back with `load_imports` once `alacritty migrate` is dropped. +// +/// Get all import paths for a configuration. +pub fn imports( + config: &Value, + recursion_limit: usize, +) -> StdResult<Vec<StdResult<PathBuf, String>>, String> { + let imports = match config.get("import") { + Some(Value::Array(imports)) => imports, + Some(_) => return Err("Invalid import type: expected a sequence".into()), + None => return Ok(Vec::new()), }; // Limit recursion to prevent infinite loops. if !imports.is_empty() && recursion_limit == 0 { - error!(target: LOG_TARGET_CONFIG, "Exceeded maximum configuration import depth"); - return Value::Null; + return Err("Exceeded maximum configuration import depth".into()); } - let mut merged = Value::Null; + let mut import_paths = Vec::new(); for import in imports { let mut path = match import { Value::String(path) => PathBuf::from(path), _ => { - error!( - target: LOG_TARGET_CONFIG, - "Invalid import element type: expected path string" - ); + import_paths.push(Err("Invalid import element type: expected path string".into())); continue; }, }; @@ -240,49 +312,42 @@ fn load_imports(config: &Value, config_paths: &mut Vec<PathBuf>, recursion_limit path = home_dir.join(stripped); } - if !path.exists() { - info!(target: LOG_TARGET_CONFIG, "Config import not found:\n {:?}", path.display()); - continue; - } - - match parse_config(&path, config_paths, recursion_limit - 1) { - Ok(config) => merged = serde_utils::merge(merged, config), - Err(err) => { - error!(target: LOG_TARGET_CONFIG, "Unable to import config {:?}: {}", path, err) - }, - } + import_paths.push(Ok(path)); } - merged + Ok(import_paths) } /// Get the location of the first found default config file paths /// according to the following order: /// -/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.yml -/// 2. $XDG_CONFIG_HOME/alacritty.yml -/// 3. $HOME/.config/alacritty/alacritty.yml -/// 4. $HOME/.alacritty.yml +/// 1. $XDG_CONFIG_HOME/alacritty/alacritty.toml +/// 2. $XDG_CONFIG_HOME/alacritty.toml +/// 3. $HOME/.config/alacritty/alacritty.toml +/// 4. $HOME/.alacritty.toml #[cfg(not(windows))] -fn installed_config() -> Option<PathBuf> { +pub fn installed_config(suffix: &str) -> Option<PathBuf> { + let file_name = format!("alacritty.{suffix}"); + // Try using XDG location by default. xdg::BaseDirectories::with_prefix("alacritty") .ok() - .and_then(|xdg| xdg.find_config_file("alacritty.yml")) + .and_then(|xdg| xdg.find_config_file(&file_name)) .or_else(|| { xdg::BaseDirectories::new() .ok() - .and_then(|fallback| fallback.find_config_file("alacritty.yml")) + .and_then(|fallback| fallback.find_config_file(&file_name)) }) .or_else(|| { if let Ok(home) = env::var("HOME") { - // Fallback path: $HOME/.config/alacritty/alacritty.yml. - let fallback = PathBuf::from(&home).join(".config/alacritty/alacritty.yml"); + // Fallback path: $HOME/.config/alacritty/alacritty.toml. + let fallback = PathBuf::from(&home).join(".config/alacritty").join(&file_name); if fallback.exists() { return Some(fallback); } - // Fallback path: $HOME/.alacritty.yml. - let fallback = PathBuf::from(&home).join(".alacritty.yml"); + // Fallback path: $HOME/.alacritty.toml. + let hidden_name = format!(".{file_name}"); + let fallback = PathBuf::from(&home).join(hidden_name); if fallback.exists() { return Some(fallback); } @@ -292,22 +357,17 @@ fn installed_config() -> Option<PathBuf> { } #[cfg(windows)] -fn installed_config() -> Option<PathBuf> { - dirs::config_dir().map(|path| path.join("alacritty\\alacritty.yml")).filter(|new| new.exists()) +pub fn installed_config(suffix: &str) -> Option<PathBuf> { + let file_name = format!("alacritty.{suffix}"); + dirs::config_dir().map(|path| path.join("alacritty").join(file_name)).filter(|new| new.exists()) } #[cfg(test)] mod tests { use super::*; - static DEFAULT_ALACRITTY_CONFIG: &str = - concat!(env!("CARGO_MANIFEST_DIR"), "/../alacritty.yml"); - #[test] - fn config_read_eof() { - let config_path: PathBuf = DEFAULT_ALACRITTY_CONFIG.into(); - let mut config = read_config(&config_path, Value::Null).unwrap(); - config.config_paths = Vec::new(); - assert_eq!(config, UiConfig::default()); + fn empty_config() { + toml::from_str::<UiConfig>("").unwrap(); } } |