Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ia27c829dbcc21a7fcfc8e6f67f9e33276a6a6964
245 lines
8.3 KiB
Rust
245 lines
8.3 KiB
Rust
use std::{collections::HashMap, path::Path};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use super::enums::{ProjectSide, ProjectType, UpdateStrategy};
|
|
use crate::error::{PakkerError, Result};
|
|
|
|
const CONFIG_NAME: &str = "pakker.json";
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(untagged)]
|
|
enum ConfigWrapper {
|
|
Pakker(Config),
|
|
Pakku { pakku: PakkerWrappedConfig },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct PakkerWrappedConfig {
|
|
pub parent: Option<ParentConfig>,
|
|
#[serde(default)]
|
|
pub parent_lock_hash: String,
|
|
#[serde(default)]
|
|
pub patches: Vec<serde_json::Value>,
|
|
#[serde(default)]
|
|
pub projects: HashMap<String, ProjectConfig>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ParentConfig {
|
|
pub id: String,
|
|
pub r#ref: String,
|
|
pub ref_type: String,
|
|
pub remote_name: String,
|
|
#[serde(rename = "type")]
|
|
pub type_: String,
|
|
pub version: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Config {
|
|
pub name: String,
|
|
pub version: String,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub author: Option<String>,
|
|
#[serde(default)]
|
|
pub overrides: Vec<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub server_overrides: Option<Vec<String>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub client_overrides: Option<Vec<String>>,
|
|
#[serde(default)]
|
|
pub paths: HashMap<String, String>,
|
|
#[serde(default)]
|
|
pub projects: Option<HashMap<String, ProjectConfig>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub export_profiles: Option<HashMap<String, crate::export::ProfileConfig>>,
|
|
#[serde(
|
|
skip_serializing_if = "Option::is_none",
|
|
rename = "exportServerSideProjectsToClient"
|
|
)]
|
|
pub export_server_side_projects_to_client: Option<bool>,
|
|
/// Number of files to select per project (defaults to 1)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub file_count_preference: Option<usize>,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Self {
|
|
name: String::new(),
|
|
version: String::new(),
|
|
description: None,
|
|
author: None,
|
|
overrides: vec!["overrides".to_string()],
|
|
server_overrides: None,
|
|
client_overrides: None,
|
|
paths: HashMap::new(),
|
|
projects: Some(HashMap::new()),
|
|
export_profiles: None,
|
|
export_server_side_projects_to_client: None,
|
|
file_count_preference: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ProjectConfig {
|
|
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
|
pub r#type: Option<ProjectType>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub side: Option<ProjectSide>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub update_strategy: Option<UpdateStrategy>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub redistributable: Option<bool>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub subpath: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub aliases: Option<Vec<String>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub export: Option<bool>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
let path = path.as_ref().join(CONFIG_NAME);
|
|
let content =
|
|
std::fs::read_to_string(&path).map_err(PakkerError::IoError)?;
|
|
|
|
match serde_json::from_str::<ConfigWrapper>(&content) {
|
|
Ok(ConfigWrapper::Pakker(config)) => {
|
|
config.validate()?;
|
|
Ok(config)
|
|
},
|
|
Ok(ConfigWrapper::Pakku { pakku }) => {
|
|
let name = pakku
|
|
.parent
|
|
.as_ref()
|
|
.map(|p| {
|
|
p.id
|
|
.split('/')
|
|
.next_back()
|
|
.unwrap_or(&p.id)
|
|
.trim_end_matches(".git")
|
|
.to_string()
|
|
})
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
|
|
let version = pakku
|
|
.parent
|
|
.as_ref()
|
|
.map_or_else(|| "unknown".to_string(), |p| p.version.clone());
|
|
|
|
Ok(Self {
|
|
name,
|
|
version,
|
|
description: None,
|
|
author: None,
|
|
overrides: vec!["overrides".to_string()],
|
|
server_overrides: None,
|
|
client_overrides: None,
|
|
paths: HashMap::new(),
|
|
projects: Some(pakku.projects),
|
|
export_profiles: None,
|
|
export_server_side_projects_to_client: None,
|
|
file_count_preference: None,
|
|
})
|
|
},
|
|
Err(e) => Err(PakkerError::InvalidConfigFile(e.to_string())),
|
|
}
|
|
}
|
|
|
|
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
|
self.validate()?;
|
|
let path = path.as_ref().join(CONFIG_NAME);
|
|
let temp_path = path.with_extension("tmp");
|
|
let content = serde_json::to_string_pretty(self)
|
|
.map_err(PakkerError::SerializationError)?;
|
|
std::fs::write(&temp_path, content)?;
|
|
std::fs::rename(temp_path, path)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn validate(&self) -> Result<()> {
|
|
if self.name.is_empty() {
|
|
return Err(PakkerError::InvalidConfigFile(
|
|
"Config name cannot be empty".to_string(),
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_project_config(&self, project_id: &str) -> Option<&ProjectConfig> {
|
|
self.projects.as_ref()?.get(project_id)
|
|
}
|
|
|
|
pub fn set_project_config(
|
|
&mut self,
|
|
project_id: String,
|
|
project_config: ProjectConfig,
|
|
) {
|
|
let projects = self.projects.get_or_insert_with(HashMap::new);
|
|
projects.insert(project_id, project_config);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_config_new() {
|
|
let config = Config {
|
|
name: "test-pack".to_string(),
|
|
version: "1.0.0".to_string(),
|
|
description: None,
|
|
author: None,
|
|
overrides: vec!["overrides".to_string()],
|
|
server_overrides: None,
|
|
client_overrides: None,
|
|
paths: HashMap::new(),
|
|
projects: None,
|
|
export_profiles: None,
|
|
export_server_side_projects_to_client: None,
|
|
file_count_preference: None,
|
|
};
|
|
assert_eq!(config.name, "test-pack");
|
|
assert_eq!(config.version, "1.0.0");
|
|
assert_eq!(config.overrides, vec!["overrides"]);
|
|
assert!(config.projects.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_serialization() {
|
|
let mut config = Config {
|
|
name: "test-pack".to_string(),
|
|
version: "1.0.0".to_string(),
|
|
description: None,
|
|
author: None,
|
|
overrides: vec!["overrides".to_string()],
|
|
server_overrides: None,
|
|
client_overrides: None,
|
|
paths: HashMap::new(),
|
|
projects: None,
|
|
export_profiles: None,
|
|
export_server_side_projects_to_client: None,
|
|
file_count_preference: None,
|
|
};
|
|
config.description = Some("A test modpack".to_string());
|
|
config.author = Some("Test Author".to_string());
|
|
|
|
let json = serde_json::to_string(&config).unwrap();
|
|
let deserialized: Config = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(deserialized.name, "test-pack");
|
|
assert_eq!(deserialized.version, "1.0.0");
|
|
assert_eq!(deserialized.description, Some("A test modpack".to_string()));
|
|
assert_eq!(deserialized.author, Some("Test Author".to_string()));
|
|
}
|
|
}
|