mirror of
https://github.com/WarrenHood/MCModpackManager.git
synced 2025-06-16 12:04:58 +01: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