1use crate::pack_format::{COBBLE_VERSION, SUPPORTED_MINECRAFT_VERSION, SUPPORTED_PACK_FORMAT};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::{Path, PathBuf};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct CobbleConfig {
8 pub project: ProjectConfig,
9 #[serde(default)]
10 pub build: BuildConfig,
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ProjectConfig {
15 pub name: String,
16 pub description: String,
17 pub namespace: String,
18 #[serde(default = "default_version")]
19 pub version: String,
20 #[serde(default = "default_pack_format")]
21 pub pack_format: String, }
23
24#[derive(Debug, Clone, Serialize, Deserialize, Default)]
25pub struct BuildConfig {
26 #[serde(default = "default_source")]
27 pub source: String,
28 #[serde(default = "default_output")]
29 pub output: String,
30 #[serde(default)]
31 pub entry_points: Vec<String>,
32}
33
34fn default_version() -> String {
35 "1.0.0".to_string()
36}
37
38fn default_pack_format() -> String {
39 SUPPORTED_PACK_FORMAT.to_string()
40}
41
42fn default_source() -> String {
43 "src".to_string()
44}
45
46fn default_output() -> String {
47 "output".to_string()
48}
49
50impl CobbleConfig {
51 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, String> {
52 let config = Self::load_unvalidated(path)?;
53 config.validate_pack_format()?;
54 Ok(config)
55 }
56
57 pub fn load_unvalidated<P: AsRef<Path>>(path: P) -> Result<Self, String> {
58 let contents =
59 fs::read_to_string(path).map_err(|e| format!("Failed to read config file: {}", e))?;
60 toml::from_str(&contents).map_err(|e| format!("Failed to parse config file: {}", e))
61 }
62
63 pub fn validate_pack_format(&self) -> Result<(), String> {
64 let pack_format = crate::pack_format::PackFormat::parse_format(&self.project.pack_format)
66 .map_err(|e| {
67 format!("Invalid pack_format '{}': {}", self.project.pack_format, e)
68 })?;
69
70 if !pack_format.is_supported() {
71 return Err(format!(
72 "Invalid pack_format: {}. Must be {} (Minecraft Java Edition {}).\n\
73 \n\
74 Cobble v{} exclusively supports Minecraft Java Edition {}.\n\
75 See https://minecraft.wiki/w/Pack_format for version compatibility.\n\
76 \n\
77 Update your cobble.toml:\n\
78 [project]\n\
79 pack_format = \"{}\"",
80 self.project.pack_format,
81 SUPPORTED_PACK_FORMAT,
82 SUPPORTED_MINECRAFT_VERSION,
83 COBBLE_VERSION,
84 SUPPORTED_MINECRAFT_VERSION,
85 SUPPORTED_PACK_FORMAT
86 ));
87 }
88
89 Ok(())
90 }
91
92 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), String> {
93 let contents = toml::to_string_pretty(self)
94 .map_err(|e| format!("Failed to serialize config: {}", e))?;
95 fs::write(path, contents).map_err(|e| format!("Failed to write config file: {}", e))
96 }
97
98 pub fn default_with_name(name: String) -> Self {
99 Self {
100 project: ProjectConfig {
101 namespace: name.to_lowercase().replace(" ", "_").replace("-", "_"),
102 description: format!("{} Data Pack", name),
103 name,
104 version: default_version(),
105 pack_format: default_pack_format(),
106 },
107 build: BuildConfig {
108 source: default_source(),
109 output: default_output(),
110 entry_points: vec![],
111 },
112 }
113 }
114
115 pub fn find_in_path<P: AsRef<Path>>(path: P) -> Option<PathBuf> {
116 const MAX_DEPTH: usize = 100;
117 Self::find_in_path_with_depth(path, 0, MAX_DEPTH)
118 }
119
120 fn find_in_path_with_depth<P: AsRef<Path>>(
121 path: P,
122 depth: usize,
123 max_depth: usize,
124 ) -> Option<PathBuf> {
125 if depth > max_depth {
126 return None;
127 }
128
129 let path = path.as_ref();
130 let config_file = path.join("cobble.toml");
131
132 if config_file.exists() {
133 return Some(config_file);
134 }
135
136 if let Some(parent) = path.parent() {
138 if parent != path {
139 return Self::find_in_path_with_depth(parent, depth + 1, max_depth);
140 }
141 }
142
143 None
144 }
145}