mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 01:58:41 +00:00 
			
		
		
		
	WIP json merging
This commit is contained in:
		
							parent
							
								
									2189ce9bce
								
							
						
					
					
						commit
						3e56632ef0
					
				
							
								
								
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -2074,6 +2074,7 @@ dependencies = [
 | 
			
		|||
 "reqwest",
 | 
			
		||||
 "semver",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "sha1",
 | 
			
		||||
 "sha2",
 | 
			
		||||
 "tempfile",
 | 
			
		||||
| 
						 | 
				
			
			@ -3194,9 +3195,9 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_json"
 | 
			
		||||
version = "1.0.125"
 | 
			
		||||
version = "1.0.128"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
 | 
			
		||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "memchr",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ pathdiff = "0.2.1"
 | 
			
		|||
reqwest = { version = "0.12.5", features = ["json"] }
 | 
			
		||||
semver = { version = "1.0.23", features = ["serde"] }
 | 
			
		||||
serde = { version = "1.0.207", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.128"
 | 
			
		||||
sha1 = "0.10.6"
 | 
			
		||||
sha2 = "0.10.8"
 | 
			
		||||
tempfile = "3.12.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										150
									
								
								mcmpmgr/src/file_merge.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								mcmpmgr/src/file_merge.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,150 @@
 | 
			
		|||
use std::{any::Any, default, str::FromStr};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy)]
 | 
			
		||||
pub enum FileType {
 | 
			
		||||
    Json,
 | 
			
		||||
    Yaml,
 | 
			
		||||
    Toml,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for FileType {
 | 
			
		||||
    type Err = anyhow::Error;
 | 
			
		||||
 | 
			
		||||
    fn from_str(s: &str) -> anyhow::Result<Self> {
 | 
			
		||||
        Ok(if s.contains("json") {
 | 
			
		||||
            FileType::Json
 | 
			
		||||
        } else if s.contains("toml") {
 | 
			
		||||
            FileType::Toml
 | 
			
		||||
        } else if s.contains("yaml") || s.contains("yml") {
 | 
			
		||||
            FileType::Yaml
 | 
			
		||||
        } else {
 | 
			
		||||
            anyhow::bail!("Unmergable file type: {s}")
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn merge_json(
 | 
			
		||||
    src: &serde_json::Value,
 | 
			
		||||
    dst: &mut serde_json::Value,
 | 
			
		||||
    overwrite_existing: bool,
 | 
			
		||||
) -> anyhow::Result<()> {
 | 
			
		||||
    if src.is_object() && dst.is_object() {
 | 
			
		||||
        let src = src.as_object().unwrap();
 | 
			
		||||
        let dst = dst.as_object_mut().unwrap();
 | 
			
		||||
 | 
			
		||||
        for (k, v) in src.iter() {
 | 
			
		||||
            if v.is_object() {
 | 
			
		||||
                let dst_v = dst.entry(k).or_insert(serde_json::json!({}));
 | 
			
		||||
                merge_json(v, dst_v, overwrite_existing)?;
 | 
			
		||||
            } else {
 | 
			
		||||
                if overwrite_existing || !dst.contains_key(k) {
 | 
			
		||||
                    dst.insert(k.to_string(), v.clone());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        // TODO: Keep track of path for better errors
 | 
			
		||||
        anyhow::bail!("Cannot merge non-objects: {src:#?} and {dst:#?}")
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_merge_json() {
 | 
			
		||||
    let src = serde_json::json!({
 | 
			
		||||
        "a": 3,
 | 
			
		||||
        "b": {
 | 
			
		||||
            "x": {
 | 
			
		||||
 | 
			
		||||
            },
 | 
			
		||||
            "y": {
 | 
			
		||||
                "test": "thing"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "c": {}
 | 
			
		||||
    });
 | 
			
		||||
    let dst = serde_json::json!({
 | 
			
		||||
        "b": {
 | 
			
		||||
            "y": {
 | 
			
		||||
                "test": "something"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "c": {
 | 
			
		||||
            "foo": "bar"
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let mut merged_overwrite = dst.clone();
 | 
			
		||||
    let mut merged_retained = dst.clone();
 | 
			
		||||
    merge_json(&src, &mut merged_overwrite, true).unwrap();
 | 
			
		||||
    merge_json(&src, &mut merged_retained, false).unwrap();
 | 
			
		||||
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_overwrite["b"]["y"]["test"] == "thing",
 | 
			
		||||
        "//b/y/test wasn't overwritten with \"thing\". src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_overwrite
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_overwrite["a"] == 3,
 | 
			
		||||
        "//a was not set to 3. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_overwrite
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_overwrite["b"]["x"].is_object(),
 | 
			
		||||
        "//b/x is not an object. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_overwrite
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_overwrite["c"]["foo"] == "bar",
 | 
			
		||||
        "//c/foo != bar. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_overwrite
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_retained["b"]["y"]["test"] == "something",
 | 
			
		||||
        "//b/y/test was overwritten. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_retained
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_retained["a"] == 3,
 | 
			
		||||
        "//a was not set to 3. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_retained
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_retained["b"]["x"].is_object(),
 | 
			
		||||
        "//b/x is not an object. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_retained
 | 
			
		||||
    );
 | 
			
		||||
    assert!(
 | 
			
		||||
        merged_retained["c"]["foo"] == "bar",
 | 
			
		||||
        "//c/foo != bar. src={}, dst={}",
 | 
			
		||||
        src,
 | 
			
		||||
        merged_retained
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Merge `src` into `dst` if it is a supported file type
 | 
			
		||||
fn merge_files(
 | 
			
		||||
    src: &str,
 | 
			
		||||
    dst: &str,
 | 
			
		||||
    overwrite_existing: bool,
 | 
			
		||||
    file_type: FileType,
 | 
			
		||||
) -> anyhow::Result<String> {
 | 
			
		||||
    Ok(match file_type {
 | 
			
		||||
        FileType::Json => {
 | 
			
		||||
            let src_val = serde_json::from_str(src)?;
 | 
			
		||||
            let mut dst_val = serde_json::from_str(dst)?;
 | 
			
		||||
            merge_json(&src_val, &mut dst_val, overwrite_existing)?;
 | 
			
		||||
            dst_val.to_string()
 | 
			
		||||
        }
 | 
			
		||||
        FileType::Yaml => todo!(),
 | 
			
		||||
        FileType::Toml => todo!(),
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
use crate::providers::DownloadSide;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
 | 
			
		||||
use std::{fmt::Display, path::Path, str::FromStr};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
 | 
			
		||||
pub struct FileMeta {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ pub struct FileMeta {
 | 
			
		|||
    /// Which side the files should be applied on
 | 
			
		||||
    pub side: DownloadSide,
 | 
			
		||||
    /// When to apply the files to the instance
 | 
			
		||||
    pub apply_policy: FileApplyPolicy
 | 
			
		||||
    pub apply_policy: FileApplyPolicy,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,11 @@ pub enum FileApplyPolicy {
 | 
			
		|||
    /// Always ensure the file or folder exactly matches that defined in the pack
 | 
			
		||||
    Always,
 | 
			
		||||
    /// Only apply the file or folder if it doesn't already exist in the pack
 | 
			
		||||
    Once
 | 
			
		||||
    Once,
 | 
			
		||||
    /// Merge into folders and files, retaining existing values in files when a file already exists
 | 
			
		||||
    MergeRetain,
 | 
			
		||||
    /// Merge into folders and files, overwriting existing values in files when a file already exists
 | 
			
		||||
    MergeOverwrite,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for FileApplyPolicy {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +31,8 @@ impl FromStr for FileApplyPolicy {
 | 
			
		|||
        match s.to_ascii_lowercase().as_str() {
 | 
			
		||||
            "always" => Ok(Self::Always),
 | 
			
		||||
            "once" => Ok(Self::Once),
 | 
			
		||||
            "mergeretain" => Ok(Self::MergeRetain),
 | 
			
		||||
            "mergeoverwrite" => Ok(Self::MergeOverwrite),
 | 
			
		||||
            _ => anyhow::bail!("Invalid apply policy {}. Expected one of: always, once", s),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +43,8 @@ impl Display for FileApplyPolicy {
 | 
			
		|||
        match self {
 | 
			
		||||
            Self::Always => write!(f, "Always"),
 | 
			
		||||
            Self::Once => write!(f, "Once"),
 | 
			
		||||
            Self::MergeRetain => write!(f, "MergeRetain"),
 | 
			
		||||
            Self::MergeOverwrite => write!(f, "MergeOverwrite"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
pub mod mod_meta;
 | 
			
		||||
pub mod file_merge;
 | 
			
		||||
pub mod file_meta;
 | 
			
		||||
pub mod mod_meta;
 | 
			
		||||
pub mod modpack;
 | 
			
		||||
pub mod profiles;
 | 
			
		||||
pub mod providers;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
mod file_merge;
 | 
			
		||||
mod file_meta;
 | 
			
		||||
mod mod_meta;
 | 
			
		||||
mod modpack;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue