mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 07:58:40 +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",
 | 
					 "reqwest",
 | 
				
			||||||
 "semver",
 | 
					 "semver",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 "sha1",
 | 
					 "sha1",
 | 
				
			||||||
 "sha2",
 | 
					 "sha2",
 | 
				
			||||||
 "tempfile",
 | 
					 "tempfile",
 | 
				
			||||||
| 
						 | 
					@ -3194,9 +3195,9 @@ dependencies = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "serde_json"
 | 
					name = "serde_json"
 | 
				
			||||||
version = "1.0.125"
 | 
					version = "1.0.128"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
 | 
					checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "itoa",
 | 
					 "itoa",
 | 
				
			||||||
 "memchr",
 | 
					 "memchr",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ pathdiff = "0.2.1"
 | 
				
			||||||
reqwest = { version = "0.12.5", features = ["json"] }
 | 
					reqwest = { version = "0.12.5", features = ["json"] }
 | 
				
			||||||
semver = { version = "1.0.23", features = ["serde"] }
 | 
					semver = { version = "1.0.23", features = ["serde"] }
 | 
				
			||||||
serde = { version = "1.0.207", features = ["derive"] }
 | 
					serde = { version = "1.0.207", features = ["derive"] }
 | 
				
			||||||
 | 
					serde_json = "1.0.128"
 | 
				
			||||||
sha1 = "0.10.6"
 | 
					sha1 = "0.10.6"
 | 
				
			||||||
sha2 = "0.10.8"
 | 
					sha2 = "0.10.8"
 | 
				
			||||||
tempfile = "3.12.0"
 | 
					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 crate::providers::DownloadSide;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					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)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
 | 
				
			||||||
pub struct FileMeta {
 | 
					pub struct FileMeta {
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,7 @@ pub struct FileMeta {
 | 
				
			||||||
    /// Which side the files should be applied on
 | 
					    /// Which side the files should be applied on
 | 
				
			||||||
    pub side: DownloadSide,
 | 
					    pub side: DownloadSide,
 | 
				
			||||||
    /// When to apply the files to the instance
 | 
					    /// When to apply the files to the instance
 | 
				
			||||||
    pub apply_policy: FileApplyPolicy
 | 
					    pub apply_policy: FileApplyPolicy,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
 | 
					#[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 ensure the file or folder exactly matches that defined in the pack
 | 
				
			||||||
    Always,
 | 
					    Always,
 | 
				
			||||||
    /// Only apply the file or folder if it doesn't already exist in the pack
 | 
					    /// 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 {
 | 
					impl FromStr for FileApplyPolicy {
 | 
				
			||||||
| 
						 | 
					@ -27,6 +31,8 @@ impl FromStr for FileApplyPolicy {
 | 
				
			||||||
        match s.to_ascii_lowercase().as_str() {
 | 
					        match s.to_ascii_lowercase().as_str() {
 | 
				
			||||||
            "always" => Ok(Self::Always),
 | 
					            "always" => Ok(Self::Always),
 | 
				
			||||||
            "once" => Ok(Self::Once),
 | 
					            "once" => Ok(Self::Once),
 | 
				
			||||||
 | 
					            "mergeretain" => Ok(Self::MergeRetain),
 | 
				
			||||||
 | 
					            "mergeoverwrite" => Ok(Self::MergeOverwrite),
 | 
				
			||||||
            _ => anyhow::bail!("Invalid apply policy {}. Expected one of: always, once", s),
 | 
					            _ => anyhow::bail!("Invalid apply policy {}. Expected one of: always, once", s),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -37,6 +43,8 @@ impl Display for FileApplyPolicy {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
            Self::Always => write!(f, "Always"),
 | 
					            Self::Always => write!(f, "Always"),
 | 
				
			||||||
            Self::Once => write!(f, "Once"),
 | 
					            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 file_meta;
 | 
				
			||||||
 | 
					pub mod mod_meta;
 | 
				
			||||||
pub mod modpack;
 | 
					pub mod modpack;
 | 
				
			||||||
pub mod profiles;
 | 
					pub mod profiles;
 | 
				
			||||||
pub mod providers;
 | 
					pub mod providers;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					mod file_merge;
 | 
				
			||||||
mod file_meta;
 | 
					mod file_meta;
 | 
				
			||||||
mod mod_meta;
 | 
					mod mod_meta;
 | 
				
			||||||
mod modpack;
 | 
					mod modpack;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue