aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/config/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/config/mod.rs')
-rw-r--r--alacritty/src/config/mod.rs202
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();
}
}