1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fs;
4use std::path::PathBuf;
5use flate2::read::GzDecoder;
6use tar::Archive;
7use crate::error::{KoreError, KoreResult};
8
9const REGISTRY_URL: &str = "https://greeble.co/kore/index.json";
10
11#[derive(Debug, Serialize, Deserialize)]
12pub struct PackageManifest {
13 pub package: PackageInfo,
14 #[serde(default)]
15 pub dependencies: HashMap<String, String>,
16}
17
18#[derive(Debug, Serialize, Deserialize)]
19pub struct PackageInfo {
20 pub name: String,
21 pub version: String,
22 #[serde(default)]
23 pub authors: Vec<String>,
24 #[serde(default)]
25 pub description: Option<String>,
26}
27
28#[derive(Debug, Deserialize)]
30struct RegistryIndex {
31 packages: HashMap<String, String>, }
33
34#[derive(Debug, Deserialize)]
35struct PackageMeta {
36 versions: HashMap<String, PackageVersion>,
37}
38
39#[derive(Debug, Deserialize)]
40struct PackageVersion {
41 url: String,
42 checksum: String,
43}
44
45impl PackageManifest {
46 pub fn default(name: &str) -> Self {
47 Self {
48 package: PackageInfo {
49 name: name.to_string(),
50 version: "0.1.0".to_string(),
51 authors: vec![],
52 description: None,
53 },
54 dependencies: HashMap::new(),
55 }
56 }
57}
58
59pub fn init_project(path: &PathBuf, name: Option<String>) -> KoreResult<()> {
60 if !path.exists() {
61 fs::create_dir_all(path).map_err(|e| KoreError::Io(e))?;
62 }
63
64 let name = name.unwrap_or_else(|| {
65 path.file_name()
66 .and_then(|s| s.to_str())
67 .unwrap_or("my_project")
68 .to_string()
69 });
70
71 let manifest = PackageManifest::default(&name);
73 let toml = toml::to_string_pretty(&manifest)
74 .map_err(|e| KoreError::runtime(format!("Failed to serialize manifest: {}", e)))?;
75
76 fs::write(path.join("kore.toml"), toml).map_err(|e| KoreError::Io(e))?;
77
78 let src_dir = path.join("src");
80 fs::create_dir_all(&src_dir).map_err(|e| KoreError::Io(e))?;
81
82 let main_src = format!(r#"
84# {} - Main Entry Point
85
86fn main():
87 println("Hello, KORE World!")
88"#, name);
89
90 fs::write(src_dir.join("main.kr"), main_src.trim()).map_err(|e| KoreError::Io(e))?;
91
92 fs::write(path.join(".gitignore"), "target/\ndeps/\n").map_err(|e| KoreError::Io(e))?;
94
95 println!(" Initialized new KORE project: {}", name);
96 Ok(())
97}
98
99pub fn load_manifest(path: &PathBuf) -> KoreResult<PackageManifest> {
100 let manifest_path = if path.ends_with("kore.toml") {
101 path.clone()
102 } else {
103 path.join("kore.toml")
104 };
105
106 if !manifest_path.exists() {
107 return Err(KoreError::runtime(format!("Manifest not found at {}", manifest_path.display())));
108 }
109
110 let content = fs::read_to_string(&manifest_path).map_err(|e| KoreError::Io(e))?;
111 let manifest: PackageManifest = toml::from_str(&content)
112 .map_err(|e| KoreError::runtime(format!("Failed to parse kore.toml: {}", e)))?;
113
114 Ok(manifest)
115}
116
117pub fn add_dependency(package_name: &str, version: Option<String>) -> KoreResult<()> {
118 println!(" Fetching registry index...");
119 let index: RegistryIndex = reqwest::blocking::get(REGISTRY_URL)
120 .map_err(|e| KoreError::runtime(format!("Failed to fetch registry: {}", e)))?
121 .json()
122 .map_err(|e| KoreError::runtime(format!("Failed to parse registry index: {}", e)))?;
123
124 let meta_path = index.packages.get(package_name)
125 .ok_or_else(|| KoreError::runtime(format!("Package '{}' not found in registry.", package_name)))?;
126
127 let meta_url = format!("https://greeble.co/kore/{}", meta_path);
128 println!(" Fetching metadata for {}...", package_name);
129
130 let meta: PackageMeta = reqwest::blocking::get(&meta_url)
131 .map_err(|e| KoreError::runtime(format!("Failed to fetch package metadata: {}", e)))?
132 .json()
133 .map_err(|e| KoreError::runtime(format!("Failed to parse package metadata: {}", e)))?;
134
135 let version_to_install = version.unwrap_or_else(|| {
137 meta.versions.keys().max().unwrap().clone()
139 });
140
141 let pkg_ver = meta.versions.get(&version_to_install)
142 .ok_or_else(|| KoreError::runtime(format!("Version {} not found for package {}", version_to_install, package_name)))?;
143
144 println!(" Resolving {} v{}...", package_name, version_to_install);
145
146 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
148 let mut manifest = load_manifest(&cwd)?;
149
150 manifest.dependencies.insert(package_name.to_string(), version_to_install.clone());
151
152 let toml = toml::to_string_pretty(&manifest)
153 .map_err(|e| KoreError::runtime(format!("Failed to serialize manifest: {}", e)))?;
154
155 fs::write(cwd.join("kore.toml"), toml).map_err(|e| KoreError::Io(e))?;
156
157 println!(" Added {} v{} to kore.toml", package_name, version_to_install);
158
159 install_package(package_name, &version_to_install, &pkg_ver.url)
161}
162
163pub fn install_all() -> KoreResult<()> {
164 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
165 let manifest = load_manifest(&cwd)?;
166
167 if manifest.dependencies.is_empty() {
168 println!(" No dependencies to install.");
169 return Ok(());
170 }
171
172 println!(" Fetching registry index...");
173 let index: RegistryIndex = reqwest::blocking::get(REGISTRY_URL)
174 .map_err(|e| KoreError::runtime(format!("Failed to fetch registry: {}", e)))?
175 .json()
176 .map_err(|e| KoreError::runtime(format!("Failed to parse registry index: {}", e)))?;
177
178 for (name, version) in manifest.dependencies {
179 if let Some(meta_path) = index.packages.get(&name) {
181 let meta_url = format!("https://greeble.co/kore/{}", meta_path);
182 let meta: PackageMeta = reqwest::blocking::get(&meta_url)
183 .map_err(|_| KoreError::runtime(format!("Failed to fetch meta for {}", name)))?
184 .json()
185 .map_err(|_| KoreError::runtime(format!("Failed to parse meta for {}", name)))?;
186
187 if let Some(v) = meta.versions.get(&version) {
188 install_package(&name, &version, &v.url)?;
189 } else {
190 eprintln!(" Version {} not found for {}", version, name);
191 }
192 } else {
193 eprintln!(" Package {} not found in registry", name);
194 }
195 }
196
197 Ok(())
198}
199
200fn install_package(name: &str, version: &str, url: &str) -> KoreResult<()> {
201 let deps_dir = PathBuf::from("deps");
202 if !deps_dir.exists() {
203 fs::create_dir_all(&deps_dir).map_err(|e| KoreError::Io(e))?;
204 }
205
206 let target_dir = deps_dir.join(name);
207 if target_dir.exists() {
208 println!(" {} v{} is already installed.", name, version);
209 return Ok(());
210 }
211
212 println!(" Downloading {} from {}...", name, url);
213 let response = reqwest::blocking::get(url)
214 .map_err(|e| KoreError::runtime(format!("Download failed: {}", e)))?;
215
216 let content = response.bytes()
217 .map_err(|e| KoreError::runtime(format!("Failed to read bytes: {}", e)))?;
218
219 println!(" Installing {}...", name);
220
221 let tar = GzDecoder::new(std::io::Cursor::new(&content));
222 let mut archive = Archive::new(tar);
223
224 archive.unpack(&target_dir).map_err(|e| KoreError::Io(e))?;
226
227 if !target_dir.join("lib.kr").exists() {
229 println!(" Warning: installed package {} might be nested.", name);
231 }
232
233 println!(" Installed {} v{}", name, version);
234 Ok(())
235}