release/0.5.0 #15

Merged
nvrl merged 16 commits from release/0.5.0 into main 2026-03-18 22:50:11 +01:00
7 changed files with 37 additions and 58 deletions
Showing only changes of commit 277d8aa151 - Show all commits

View File

@@ -1,14 +1,10 @@
use std::io;
use thiserror::Error; use thiserror::Error;
/// Custom error types for the mould application. /// Custom error types for the mould application.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum MouldError { pub enum MouldError {
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[from] io::Error), Io(#[from] std::io::Error),
#[error("Format error: {0}")]
Format(String),
#[error("File not found: {0}")] #[error("File not found: {0}")]
FileNotFound(String), FileNotFound(String),

View File

@@ -1,12 +1,12 @@
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
use std::fs; use std::fs;
use std::io::{self, Write}; use std::io::Write;
use std::path::Path; use std::path::Path;
pub struct EnvHandler; pub struct EnvHandler;
impl FormatHandler for EnvHandler { impl FormatHandler for EnvHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> { fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
let content = fs::read_to_string(path)?; let content = fs::read_to_string(path)?;
let mut vars = Vec::new(); let mut vars = Vec::new();
@@ -35,7 +35,7 @@ impl FormatHandler for EnvHandler {
Ok(vars) Ok(vars)
} }
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
let mut file = fs::File::create(path)?; let mut file = fs::File::create(path)?;
for var in vars { for var in vars {
if !var.is_group { if !var.is_group {

View File

@@ -1,7 +1,6 @@
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType}; use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType};
use serde_json::{Map, Value}; use serde_json::{Map, Value};
use std::fs; use std::fs;
use std::io;
use std::path::Path; use std::path::Path;
pub struct HierarchicalHandler { pub struct HierarchicalHandler {
@@ -13,50 +12,40 @@ impl HierarchicalHandler {
Self { format_type } Self { format_type }
} }
fn read_value(&self, path: &Path) -> io::Result<Value> { fn read_value(&self, path: &Path) -> anyhow::Result<Value> {
let content = fs::read_to_string(path)?; let content = fs::read_to_string(path)?;
let value = match self.format_type { let value = match self.format_type {
FormatType::Json => serde_json::from_str(&content) FormatType::Json => serde_json::from_str(&content)?,
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, FormatType::Yaml => serde_yaml::from_str(&content)?,
FormatType::Yaml => serde_yaml::from_str(&content) FormatType::Toml => toml::from_str(&content)?,
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, FormatType::Xml => xml_to_json(&content)?,
FormatType::Toml => toml::from_str(&content)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
FormatType::Xml => xml_to_json(&content)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
_ => unreachable!(), _ => unreachable!(),
}; };
Ok(value) Ok(value)
} }
fn write_value(&self, path: &Path, value: &Value) -> io::Result<()> { fn write_value(&self, path: &Path, value: &Value) -> anyhow::Result<()> {
let content = match self.format_type { let content = match self.format_type {
FormatType::Json => serde_json::to_string_pretty(value) FormatType::Json => serde_json::to_string_pretty(value)?,
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?, FormatType::Yaml => serde_yaml::to_string(value)?,
FormatType::Yaml => serde_yaml::to_string(value)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
FormatType::Toml => { FormatType::Toml => {
// toml requires the root to be a table // toml requires the root to be a table
if value.is_object() { if value.is_object() {
let toml_value: toml::Value = serde_json::from_value(value.clone()) let toml_value: toml::Value = serde_json::from_value(value.clone())?;
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; toml::to_string_pretty(&toml_value)?
toml::to_string_pretty(&toml_value)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
} else { } else {
return Err(io::Error::new( anyhow::bail!("Root of TOML must be an object");
io::ErrorKind::InvalidData,
"Root of TOML must be an object",
));
} }
} }
FormatType::Xml => json_to_xml(value), FormatType::Xml => json_to_xml(value),
_ => unreachable!(), _ => unreachable!(),
}; };
fs::write(path, content) fs::write(path, content)?;
Ok(())
} }
} }
fn xml_to_json(content: &str) -> io::Result<Value> { fn xml_to_json(content: &str) -> anyhow::Result<Value> {
use quick_xml::reader::Reader; use quick_xml::reader::Reader;
use quick_xml::events::Event; use quick_xml::events::Event;
@@ -64,7 +53,7 @@ fn xml_to_json(content: &str) -> io::Result<Value> {
reader.config_mut().trim_text(true); reader.config_mut().trim_text(true);
let mut buf = Vec::new(); let mut buf = Vec::new();
fn parse_recursive(reader: &mut Reader<&[u8]>) -> io::Result<Value> { fn parse_recursive(reader: &mut Reader<&[u8]>) -> anyhow::Result<Value> {
let mut map = Map::new(); let mut map = Map::new();
let mut text = String::new(); let mut text = String::new();
let mut buf = Vec::new(); let mut buf = Vec::new();
@@ -276,14 +265,14 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
} }
impl FormatHandler for HierarchicalHandler { impl FormatHandler for HierarchicalHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> { fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
let value = self.read_value(path)?; let value = self.read_value(path)?;
let mut vars = Vec::new(); let mut vars = Vec::new();
flatten(&value, "", 0, "", &mut vars); flatten(&value, "", 0, "", &mut vars);
Ok(vars) Ok(vars)
} }
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
let mut root = Value::Object(Map::new()); let mut root = Value::Object(Map::new());
for var in vars { for var in vars {
if !var.is_group { if !var.is_group {

View File

@@ -1,14 +1,12 @@
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
use ini::Ini; use ini::Ini;
use std::io;
use std::path::Path; use std::path::Path;
pub struct IniHandler; pub struct IniHandler;
impl FormatHandler for IniHandler { impl FormatHandler for IniHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> { fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
let conf = Ini::load_from_file(path) let conf = Ini::load_from_file(path)?;
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let mut vars = Vec::new(); let mut vars = Vec::new();
for (section, prop) in &conf { for (section, prop) in &conf {
@@ -52,7 +50,7 @@ impl FormatHandler for IniHandler {
Ok(vars) Ok(vars)
} }
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
let mut conf = Ini::new(); let mut conf = Ini::new();
for var in vars { for var in vars {
if !var.is_group { if !var.is_group {
@@ -67,8 +65,8 @@ impl FormatHandler for IniHandler {
} }
} }
} }
conf.write_to_file(path) conf.write_to_file(path)?;
.map_err(io::Error::other) Ok(())
} }
} }

View File

@@ -1,4 +1,3 @@
use std::io;
use std::path::Path; use std::path::Path;
pub mod env; pub mod env;
@@ -46,8 +45,8 @@ pub enum FormatType {
} }
pub trait FormatHandler { pub trait FormatHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>>; fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>>;
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> { fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> anyhow::Result<()> {
if !path.exists() { if !path.exists() {
return Ok(()); return Ok(());
} }
@@ -82,7 +81,7 @@ pub trait FormatHandler {
Ok(()) Ok(())
} }
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()>; fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()>;
} }
pub fn detect_format(path: &Path, override_format: Option<String>) -> FormatType { pub fn detect_format(path: &Path, override_format: Option<String>) -> FormatType {

View File

@@ -1,13 +1,13 @@
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType}; use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
use java_properties::{LineContent, PropertiesIter, PropertiesWriter}; use java_properties::{LineContent, PropertiesIter, PropertiesWriter};
use std::fs::File; use std::fs::File;
use std::io::{self, BufReader, BufWriter}; use std::io::{BufReader, BufWriter};
use std::path::Path; use std::path::Path;
pub struct PropertiesHandler; pub struct PropertiesHandler;
impl FormatHandler for PropertiesHandler { impl FormatHandler for PropertiesHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> { fn parse(&self, path: &Path) -> anyhow::Result<Vec<ConfigItem>> {
let file = File::open(path)?; let file = File::open(path)?;
let reader = BufReader::new(file); let reader = BufReader::new(file);
let iter = PropertiesIter::new(reader); let iter = PropertiesIter::new(reader);
@@ -16,7 +16,7 @@ impl FormatHandler for PropertiesHandler {
let mut groups = std::collections::HashSet::new(); let mut groups = std::collections::HashSet::new();
for line_result in iter { for line_result in iter {
let line = line_result.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; let line = line_result?;
if let LineContent::KVPair(path, value) = line.consume_content() { if let LineContent::KVPair(path, value) = line.consume_content() {
// Add groups based on dot notation // Add groups based on dot notation
@@ -62,7 +62,7 @@ impl FormatHandler for PropertiesHandler {
Ok(vars) Ok(vars)
} }
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> anyhow::Result<()> {
let file = File::create(path)?; let file = File::create(path)?;
let writer = BufWriter::new(file); let writer = BufWriter::new(file);
let mut prop_writer = PropertiesWriter::new(writer); let mut prop_writer = PropertiesWriter::new(writer);
@@ -72,12 +72,12 @@ impl FormatHandler for PropertiesHandler {
let val = var.value.as_deref() let val = var.value.as_deref()
.or(var.template_value.as_deref()) .or(var.template_value.as_deref())
.unwrap_or(""); .unwrap_or("");
prop_writer.write(&var.path, val) prop_writer.write(&var.path, val)?;
.map_err(io::Error::other)?;
} }
} }
prop_writer.finish().map_err(io::Error::other) prop_writer.finish()?;
Ok(())
} }
} }

View File

@@ -96,10 +96,7 @@ fn main() -> anyhow::Result<()> {
} }
} else if vars.is_empty() { } else if vars.is_empty() {
// Fallback if no template and active is empty // Fallback if no template and active is empty
vars = handler.parse(&input_path).map_err(|e| { vars = handler.parse(&input_path)?;
error!("Failed to parse input file: {}", e);
MouldError::Format(format!("Failed to parse {}: {}", input_path.display(), e))
})?;
} }
if vars.is_empty() { if vars.is_empty() {