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
6 changed files with 55 additions and 161 deletions
Showing only changes of commit b8c49d4c13 - Show all commits

2
Cargo.lock generated
View File

@@ -914,7 +914,7 @@ dependencies = [
[[package]] [[package]]
name = "mould" name = "mould"
version = "0.4.3" version = "0.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",

View File

@@ -35,53 +35,6 @@ impl FormatHandler for EnvHandler {
Ok(vars) Ok(vars)
} }
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
if !path.exists() {
return Ok(());
}
let content = fs::read_to_string(path)?;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, val)) = line.split_once('=') {
let key = key.trim();
let parsed_val = val.trim().trim_matches('"').trim_matches('\'').to_string();
if let Some(var) = vars.iter_mut().find(|v| v.key == key) {
if var.value.as_deref() != Some(&parsed_val) {
var.value = Some(parsed_val);
var.status = ItemStatus::Modified;
}
} else {
vars.push(ConfigItem {
key: key.to_string(),
path: key.to_string(),
value: Some(parsed_val),
template_value: None,
default_value: None,
depth: 0,
is_group: false,
status: ItemStatus::MissingFromTemplate,
value_type: ValueType::String,
});
}
}
}
// Mark missing from active
for var in vars.iter_mut() {
if var.status == ItemStatus::Present && var.value.is_none() {
var.status = ItemStatus::MissingFromActive;
}
}
Ok(())
}
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
let mut file = fs::File::create(path)?; let mut file = fs::File::create(path)?;
for var in vars { for var in vars {
@@ -126,27 +79,36 @@ mod tests {
#[test] #[test]
fn test_merge_env() { fn test_merge_env() {
let mut example_file = NamedTempFile::new().unwrap();
writeln!(example_file, "KEY1=default1\nKEY2=default2").unwrap();
let handler = EnvHandler; let handler = EnvHandler;
let mut vars = handler.parse(example_file.path()).unwrap();
let mut env_file = NamedTempFile::new().unwrap(); let mut env_file = NamedTempFile::new().unwrap();
writeln!(env_file, "KEY1=custom1\nKEY3=custom3").unwrap(); writeln!(env_file, "KEY1=custom1\nKEY3=custom3").unwrap();
let mut vars = handler.parse(env_file.path()).unwrap(); // Active vars
handler.merge(env_file.path(), &mut vars).unwrap(); let mut example_file = NamedTempFile::new().unwrap();
writeln!(example_file, "KEY1=default1\nKEY2=default2").unwrap();
handler.merge(example_file.path(), &mut vars).unwrap(); // Merge template into active
// Should preserve order of active, then append template
assert_eq!(vars.len(), 3); assert_eq!(vars.len(), 3);
// Active key that exists in template
assert_eq!(vars[0].key, "KEY1"); assert_eq!(vars[0].key, "KEY1");
assert_eq!(vars[0].value.as_deref(), Some("custom1")); assert_eq!(vars[0].value.as_deref(), Some("custom1")); // Keeps active value
assert_eq!(vars[0].template_value.as_deref(), Some("default1")); // Gets template default
assert_eq!(vars[0].status, ItemStatus::Modified); assert_eq!(vars[0].status, ItemStatus::Modified);
assert_eq!(vars[1].key, "KEY2"); // Active key that DOES NOT exist in template
assert_eq!(vars[1].value.as_deref(), Some("default2")); assert_eq!(vars[1].key, "KEY3");
assert_eq!(vars[1].value.as_deref(), Some("custom3"));
assert_eq!(vars[1].status, ItemStatus::Present);
assert_eq!(vars[2].key, "KEY3"); // Template key that DOES NOT exist in active
assert_eq!(vars[2].value.as_deref(), Some("custom3")); assert_eq!(vars[2].key, "KEY2");
assert_eq!(vars[2].status, ItemStatus::MissingFromTemplate); assert_eq!(vars[2].value.as_deref(), None); // Missing from active
assert_eq!(vars[2].template_value.as_deref(), Some("default2"));
assert_eq!(vars[2].status, ItemStatus::MissingFromActive);
} }
#[test] #[test]

View File

@@ -283,43 +283,6 @@ impl FormatHandler for HierarchicalHandler {
Ok(vars) Ok(vars)
} }
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
if !path.exists() {
return Ok(());
}
let existing_value = self.read_value(path)?;
let mut existing_vars = Vec::new();
flatten(&existing_value, "", 0, "", &mut existing_vars);
for var in vars.iter_mut() {
if let Some(existing) = existing_vars.iter().find(|v| v.path == var.path) {
if var.value != existing.value {
var.value = existing.value.clone();
var.status = ItemStatus::Modified;
}
} else {
var.status = ItemStatus::MissingFromActive;
}
}
// Find keys in active that are not in template
let mut to_add = Vec::new();
for existing in existing_vars {
if !vars.iter().any(|v| v.path == existing.path) {
let mut new_item = existing.clone();
new_item.status = ItemStatus::MissingFromTemplate;
new_item.template_value = None;
new_item.default_value = None;
to_add.push(new_item);
}
}
// Basic insertion logic for extra keys (could be improved to insert at correct depth/position)
vars.extend(to_add);
Ok(())
}
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
let mut root = Value::Object(Map::new()); let mut root = Value::Object(Map::new());
for var in vars { for var in vars {

View File

@@ -52,38 +52,6 @@ impl FormatHandler for IniHandler {
Ok(vars) Ok(vars)
} }
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
if !path.exists() {
return Ok(());
}
let existing_vars = self.parse(path).unwrap_or_default();
for var in vars.iter_mut() {
if let Some(existing) = existing_vars.iter().find(|v| v.path == var.path) {
if var.value != existing.value {
var.value = existing.value.clone();
var.status = ItemStatus::Modified;
}
} else {
var.status = ItemStatus::MissingFromActive;
}
}
// Add items from active that are not in template
for existing in existing_vars {
if !vars.iter().any(|v| v.path == existing.path) {
let mut new_item = existing.clone();
new_item.status = ItemStatus::MissingFromTemplate;
new_item.template_value = None;
new_item.default_value = None;
vars.push(new_item);
}
}
Ok(())
}
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
let mut conf = Ini::new(); let mut conf = Ini::new();
for var in vars { for var in vars {

View File

@@ -10,7 +10,6 @@ pub mod properties;
pub enum ItemStatus { pub enum ItemStatus {
Present, Present,
MissingFromActive, MissingFromActive,
MissingFromTemplate,
Modified, Modified,
} }
@@ -48,7 +47,41 @@ pub enum FormatType {
pub trait FormatHandler { pub trait FormatHandler {
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>>; fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>>;
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()>; fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
if !path.exists() {
return Ok(());
}
let template_vars = self.parse(path).unwrap_or_default();
for var in vars.iter_mut() {
if let Some(template_var) = template_vars.iter().find(|v| v.path == var.path) {
var.template_value = template_var.value.clone();
var.default_value = template_var.value.clone();
if var.value != template_var.value {
var.status = ItemStatus::Modified;
} else {
var.status = ItemStatus::Present;
}
} else {
// Exists in active, but not in template
var.status = ItemStatus::Present;
}
}
// Add items from template that are missing in active
for template_var in template_vars {
if !vars.iter().any(|v| v.path == template_var.path) {
let mut new_item = template_var.clone();
new_item.status = ItemStatus::MissingFromActive;
new_item.value = None;
vars.push(new_item);
}
}
Ok(())
}
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()>; fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()>;
} }

View File

@@ -61,38 +61,6 @@ impl FormatHandler for PropertiesHandler {
Ok(vars) Ok(vars)
} }
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
if !path.exists() {
return Ok(());
}
let existing_vars = self.parse(path).unwrap_or_default();
for var in vars.iter_mut() {
if let Some(existing) = existing_vars.iter().find(|v| v.path == var.path) {
if var.value != existing.value {
var.value = existing.value.clone();
var.status = ItemStatus::Modified;
}
} else {
var.status = ItemStatus::MissingFromActive;
}
}
// Add items from active that are not in template
for existing in existing_vars {
if !vars.iter().any(|v| v.path == existing.path) {
let mut new_item = existing.clone();
new_item.status = ItemStatus::MissingFromTemplate;
new_item.template_value = None;
new_item.default_value = None;
vars.push(new_item);
}
}
Ok(vars.sort_by(|a, b| a.path.cmp(&b.path)))
}
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> { fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
let mut props = HashMap::new(); let mut props = HashMap::new();
for var in vars { for var in vars {