use crate::fs;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::fmt::Debug;
use std::path::Path;
use tracing::{instrument, trace};
pub use crate::yaml_error::YamlError;
pub use serde_norway as serde_yaml;
pub use serde_norway::{
Mapping as YamlMapping, Number as YamlNumber, Sequence as YamlSequence, Value as YamlValue,
};
#[inline]
#[instrument(name = "merge_yaml", skip_all)]
pub fn merge(prev: &YamlValue, next: &YamlValue) -> YamlValue {
let mut merged = prev.to_owned();
merge_into(&mut merged, next);
merged
}
fn merge_into(prev: &mut YamlValue, next: &YamlValue) {
match (prev, next) {
(YamlValue::Mapping(prev_object), YamlValue::Mapping(next_object)) => {
for (key, value) in next_object {
if let Some(prev_value) = prev_object.get_mut(key) {
merge_into(prev_value, value);
} else {
prev_object.insert(key.to_owned(), value.to_owned());
}
}
}
(prev, next) => *prev = next.to_owned(),
}
}
#[inline]
#[instrument(name = "parse_yaml", skip(data))]
pub fn parse<D>(data: impl AsRef<str>) -> Result<D, YamlError>
where
D: DeserializeOwned,
{
trace!("Parsing YAML");
serde_norway::from_str(data.as_ref()).map_err(|error| YamlError::Parse {
error: Box::new(error),
})
}
#[inline]
#[instrument(name = "format_yaml", skip(data))]
pub fn format<D>(data: &D) -> Result<String, YamlError>
where
D: ?Sized + Serialize,
{
trace!("Formatting YAML");
serde_norway::to_string(&data).map_err(|error| YamlError::Format {
error: Box::new(error),
})
}
#[inline]
#[instrument(name = "format_yaml_with_identation", skip(data))]
pub fn format_with_identation<D>(data: &D, indent: &str) -> Result<String, YamlError>
where
D: ?Sized + Serialize,
{
trace!("Formatting YAML with preserved indentation");
let data = serde_norway::to_string(data).map_err(|error| YamlError::Format {
error: Box::new(error),
})?;
let data = data.trim();
if indent != " " {
return Ok(format_with_custom_indentation(data, indent));
}
Ok(data.to_owned())
}
fn format_with_custom_indentation(data: &str, indent: &str) -> String {
let mut output = String::with_capacity(data.len());
for (index, line) in data.split('\n').enumerate() {
if index > 0 {
output.push('\n');
}
if !line.starts_with(" ") {
output.push_str(line);
continue;
}
let leading_spaces = line
.as_bytes()
.iter()
.take_while(|char| **char == b' ')
.count();
for _ in 0..(leading_spaces / 2) {
output.push_str(indent);
}
output.push_str(&line[leading_spaces..]);
}
output
}
#[inline]
#[instrument(name = "read_yaml")]
pub fn read_file<D>(path: impl AsRef<Path> + Debug) -> Result<D, YamlError>
where
D: DeserializeOwned,
{
let path = path.as_ref();
let contents = fs::read_file(path)?;
trace!(file = ?path, "Reading YAML file");
serde_norway::from_str(&contents).map_err(|error| YamlError::ReadFile {
path: path.to_path_buf(),
error: Box::new(error),
})
}
#[inline]
#[instrument(name = "write_yaml", skip(data))]
pub fn write_file<D>(path: impl AsRef<Path> + Debug, data: &D) -> Result<(), YamlError>
where
D: ?Sized + Serialize,
{
let path = path.as_ref();
trace!(file = ?path, "Writing YAML file");
let data = serde_norway::to_string(&data).map_err(|error| YamlError::WriteFile {
path: path.to_path_buf(),
error: Box::new(error),
})?;
fs::write_file(path, data)?;
Ok(())
}
#[cfg(feature = "editor-config")]
#[inline]
#[instrument(name = "write_yaml_with_config", skip(data))]
pub fn write_file_with_config<D>(path: impl AsRef<Path> + Debug, data: &D) -> Result<(), YamlError>
where
D: ?Sized + Serialize,
{
trace!(file = ?path, "Writing YAML file with .editorconfig");
let path = path.as_ref();
let editor_config = fs::get_editor_config_props(path)?;
let mut data = format_with_identation(data, &editor_config.indent)?;
editor_config.apply_eof(&mut data);
fs::write_file(path, data)?;
Ok(())
}