release/0.5.0 #15
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user