Skip to main content

kore/
packager.rs

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// Registry Structures
29#[derive(Debug, Deserialize)]
30struct RegistryIndex {
31    packages: HashMap<String, String>, // name -> meta.json path
32}
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    // Create kore.toml
72    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    // Create src directory
79    let src_dir = path.join("src");
80    fs::create_dir_all(&src_dir).map_err(|e| KoreError::Io(e))?;
81
82    // Create main.kr
83    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    // Create .gitignore
93    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    // Determine version
136    let version_to_install = version.unwrap_or_else(|| {
137        // Pick latest (naive)
138        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    // Update kore.toml
147    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 it
160    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        // Resolve URL (duplicate logic for now, proper solver later)
180        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    // Unpack to target directory
225    archive.unpack(&target_dir).map_err(|e| KoreError::Io(e))?;
226
227    // Verify lib.kr exists (optional safety check)
228    if !target_dir.join("lib.kr").exists() {
229        // If the package was packed with a root folder (e.g. package-1.0.0/), we might need to handle stripping
230        println!(" Warning: installed package {} might be nested.", name);
231    }
232
233    println!(" Installed {} v{}", name, version);
234    Ok(())
235}