1pub mod build;
5pub mod cli;
6pub mod dependency;
7pub mod lockfile;
8pub mod registry;
9
10use crate::errors::{CompileError, Result};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::fs;
14use std::path::{Path, PathBuf};
15
16use dependency::{DependencyResolver, Package, Version, VersionRequirement};
17use lockfile::{Lockfile, LockedPackage, PackageSource};
18use registry::RegistryClient;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PackageManifest {
23 pub name: String,
25
26 pub version: String,
28
29 pub description: Option<String>,
31
32 pub authors: Vec<String>,
34
35 pub license: Option<String>,
37
38 pub dependencies: HashMap<String, Dependency>,
40
41 pub dev_dependencies: HashMap<String, Dependency>,
43
44 pub build_dependencies: HashMap<String, Dependency>,
46
47 pub main: Option<String>,
49
50 pub lib: Option<String>,
52
53 pub bin: Vec<BinaryTarget>,
55
56 pub examples: Vec<ExampleTarget>,
58
59 pub tests: Vec<TestTarget>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum Dependency {
67 Version(String),
69
70 Detailed {
72 version: Option<String>,
73 path: Option<String>,
74 git: Option<String>,
75 branch: Option<String>,
76 tag: Option<String>,
77 rev: Option<String>,
78 features: Vec<String>,
79 optional: bool,
80 },
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct BinaryTarget {
86 pub name: String,
87 pub path: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ExampleTarget {
93 pub name: String,
94 pub path: String,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TestTarget {
100 pub name: String,
101 pub path: String,
102}
103
104pub struct PackageManager {
106 cache_dir: PathBuf,
108
109 registry_url: String,
111
112 #[allow(dead_code)]
114 manifests: HashMap<String, PackageManifest>,
115
116 registry_client: Option<RegistryClient>,
118
119 resolver: DependencyResolver,
121
122 lockfile: Option<Lockfile>,
124}
125
126impl PackageManager {
127 pub fn new() -> Result<Self> {
129 let home_dir = dirs::home_dir()
130 .ok_or_else(|| CompileError::Generic("Could not find home directory".to_string()))?;
131
132 let cache_dir = home_dir.join(".palladium").join("cache");
133
134 if !cache_dir.exists() {
136 fs::create_dir_all(&cache_dir).map_err(CompileError::IoError)?;
137 }
138
139 let registry_url = "https://packages.palladium-lang.org".to_string();
140 let registry_client = RegistryClient::new(registry_url.clone(), cache_dir.clone()).ok();
141
142 Ok(Self {
143 cache_dir,
144 registry_url,
145 manifests: HashMap::new(),
146 registry_client,
147 resolver: DependencyResolver::new(),
148 lockfile: None,
149 })
150 }
151
152 pub fn load_manifest(path: &Path) -> Result<PackageManifest> {
154 let content = fs::read_to_string(path).map_err(CompileError::IoError)?;
155
156 Self::parse_manifest(&content)
159 }
160
161 fn parse_manifest(content: &str) -> Result<PackageManifest> {
163 let mut manifest = PackageManifest {
175 name: String::new(),
176 version: String::new(),
177 description: None,
178 authors: Vec::new(),
179 license: None,
180 dependencies: HashMap::new(),
181 dev_dependencies: HashMap::new(),
182 build_dependencies: HashMap::new(),
183 main: None,
184 lib: None,
185 bin: Vec::new(),
186 examples: Vec::new(),
187 tests: Vec::new(),
188 };
189
190 let mut current_section = "";
191
192 for line in content.lines() {
193 let line = line.trim();
194
195 if line.is_empty() || line.starts_with("//") || line.starts_with("#") {
197 continue;
198 }
199
200 if line.starts_with('[') && line.ends_with(']') {
202 current_section = &line[1..line.len() - 1];
203 continue;
204 }
205
206 if let Some(eq_pos) = line.find('=') {
208 let key = line[..eq_pos].trim();
209 let value = line[eq_pos + 1..].trim();
210
211 match current_section {
212 "" => {
213 match key {
215 "name" => manifest.name = Self::parse_string(value)?,
216 "version" => manifest.version = Self::parse_string(value)?,
217 "description" => {
218 manifest.description = Some(Self::parse_string(value)?)
219 }
220 "license" => manifest.license = Some(Self::parse_string(value)?),
221 "main" => manifest.main = Some(Self::parse_string(value)?),
222 "lib" => manifest.lib = Some(Self::parse_string(value)?),
223 "authors" => manifest.authors = Self::parse_string_array(value)?,
224 _ => {} }
226 }
227 "dependencies" => {
228 let dep = Self::parse_dependency(value)?;
229 manifest.dependencies.insert(key.to_string(), dep);
230 }
231 "dev-dependencies" => {
232 let dep = Self::parse_dependency(value)?;
233 manifest.dev_dependencies.insert(key.to_string(), dep);
234 }
235 "build-dependencies" => {
236 let dep = Self::parse_dependency(value)?;
237 manifest.build_dependencies.insert(key.to_string(), dep);
238 }
239 _ => {} }
241 }
242 }
243
244 if manifest.name.is_empty() {
246 return Err(CompileError::Generic(
247 "Package name is required".to_string(),
248 ));
249 }
250 if manifest.version.is_empty() {
251 return Err(CompileError::Generic(
252 "Package version is required".to_string(),
253 ));
254 }
255
256 Ok(manifest)
257 }
258
259 fn parse_string(value: &str) -> Result<String> {
261 if value.starts_with('"') && value.ends_with('"') {
262 Ok(value[1..value.len() - 1].to_string())
263 } else {
264 Err(CompileError::Generic(format!(
265 "Expected quoted string, got: {}",
266 value
267 )))
268 }
269 }
270
271 fn parse_string_array(value: &str) -> Result<Vec<String>> {
273 if value.starts_with('[') && value.ends_with(']') {
274 let inner = &value[1..value.len() - 1];
275 let mut result = Vec::new();
276
277 for item in inner.split(',') {
278 let item = item.trim();
279 if !item.is_empty() {
280 result.push(Self::parse_string(item)?);
281 }
282 }
283
284 Ok(result)
285 } else {
286 Err(CompileError::Generic(format!(
287 "Expected array, got: {}",
288 value
289 )))
290 }
291 }
292
293 fn parse_dependency(value: &str) -> Result<Dependency> {
295 if value.starts_with('"') && value.ends_with('"') {
296 Ok(Dependency::Version(value[1..value.len() - 1].to_string()))
298 } else if value.starts_with('{') && value.ends_with('}') {
299 Ok(Dependency::Version("*".to_string()))
303 } else {
304 Err(CompileError::Generic(format!(
305 "Invalid dependency format: {}",
306 value
307 )))
308 }
309 }
310
311 pub fn init(name: &str, path: &Path) -> Result<()> {
313 let src_dir = path.join("src");
315 if !src_dir.exists() {
316 fs::create_dir_all(&src_dir).map_err(CompileError::IoError)?;
317 }
318
319 let manifest = PackageManifest {
321 name: name.to_string(),
322 version: "0.1.0".to_string(),
323 description: Some("A new Palladium package".to_string()),
324 authors: vec![Self::get_default_author()],
325 license: Some("MIT".to_string()),
326 dependencies: HashMap::new(),
327 dev_dependencies: HashMap::new(),
328 build_dependencies: HashMap::new(),
329 main: None,
330 lib: None,
331 bin: Vec::new(),
332 examples: Vec::new(),
333 tests: Vec::new(),
334 };
335
336 let manifest_path = path.join("package.pd");
337 let manifest_content = Self::manifest_to_string(&manifest);
338 fs::write(&manifest_path, manifest_content).map_err(CompileError::IoError)?;
339
340 let main_path = src_dir.join("main.pd");
342 let main_content = r#"// Entry point for the package
343
344fn main() {
345 print("Hello from {}!\n");
346}
347"#
348 .replace("{}", name);
349
350 fs::write(&main_path, main_content).map_err(CompileError::IoError)?;
351
352 println!("✅ Created package '{}' at {}", name, path.display());
353
354 Ok(())
355 }
356
357 fn get_default_author() -> String {
359 if let Ok(output) = std::process::Command::new("git")
361 .args(["config", "--global", "user.name"])
362 .output()
363 {
364 if output.status.success() {
365 if let Ok(name) = String::from_utf8(output.stdout) {
366 let name = name.trim();
367
368 if let Ok(email_output) = std::process::Command::new("git")
370 .args(["config", "--global", "user.email"])
371 .output()
372 {
373 if email_output.status.success() {
374 if let Ok(email) = String::from_utf8(email_output.stdout) {
375 let email = email.trim();
376 return format!("{} <{}>", name, email);
377 }
378 }
379 }
380
381 return name.to_string();
382 }
383 }
384 }
385
386 if let Ok(user) = std::env::var("USER") {
388 return user;
389 }
390
391 "Unknown Author".to_string()
392 }
393
394 pub fn install(&mut self) -> Result<()> {
396 let manifest_path = Path::new("package.pd");
398 let manifest = Self::load_manifest(manifest_path)?;
399
400 println!("📦 Installing dependencies for '{}'...", manifest.name);
401
402 let lockfile_path = Path::new("package.lock");
404 if lockfile_path.exists() {
405 println!("🔒 Found lockfile, installing exact versions...");
406 self.lockfile = Some(Lockfile::load(lockfile_path)?);
407 return self.install_from_lockfile();
408 }
409
410 println!("🔍 Resolving dependencies...");
412 let resolved = self.resolve_dependencies(&manifest)?;
413
414 let mut lockfile = Lockfile::new(&manifest.name, &manifest.version);
416
417 for (package_name, version) in &resolved.packages {
419 if package_name == &manifest.name {
420 continue; }
422
423 println!("📥 Installing {} v{}...", package_name, version);
424
425 if let Some(registry) = &self.registry_client {
426 let package_path = registry.download_package(package_name, &version.to_string())?;
427
428 lockfile.add_package(LockedPackage {
430 name: package_name.clone(),
431 version: version.to_string(),
432 source: PackageSource::Registry {
433 url: self.registry_url.clone(),
434 },
435 dependencies: vec![], checksum: "TODO".to_string(), });
438
439 println!(" ✅ Installed to {}", package_path.display());
440 } else {
441 return Err(CompileError::Generic(
442 "Registry client not available".to_string(),
443 ));
444 }
445 }
446
447 lockfile.save(lockfile_path)?;
449 println!("🔒 Created lockfile");
450
451 println!("✅ Installation complete! {} packages installed", resolved.packages.len() - 1);
452 Ok(())
453 }
454
455 fn install_from_lockfile(&mut self) -> Result<()> {
457 let lockfile = self.lockfile.as_ref().unwrap();
458
459 lockfile.verify_checksums(&self.cache_dir)?;
461
462 for package in &lockfile.packages {
464 let package_dir = self.cache_dir.join(&package.name).join(&package.version);
465
466 if !package_dir.exists() {
467 println!("📥 Installing {} v{}...", package.name, package.version);
468
469 if let Some(registry) = &self.registry_client {
470 registry.download_package(&package.name, &package.version)?;
471 println!(" ✅ Installed");
472 } else {
473 return Err(CompileError::Generic(
474 "Registry client not available".to_string(),
475 ));
476 }
477 }
478 }
479
480 println!("✅ All dependencies installed from lockfile");
481 Ok(())
482 }
483
484 fn resolve_dependencies(&mut self, manifest: &PackageManifest) -> Result<dependency::ResolvedDependencies> {
486 if let Some(registry) = &self.registry_client {
488 let available = registry.get_all_packages()?;
489 for package in available {
490 self.resolver.add_available_package(package);
491 }
492 }
493
494 let mut root_deps = HashMap::new();
496 for (name, dep) in &manifest.dependencies {
497 let version_req = match dep {
498 Dependency::Version(v) => VersionRequirement::parse(v)?,
499 Dependency::Detailed { version, .. } => {
500 if let Some(v) = version {
501 VersionRequirement::parse(v)?
502 } else {
503 VersionRequirement::Wildcard
504 }
505 }
506 };
507 root_deps.insert(name.clone(), version_req);
508 }
509
510 let root_package = Package {
511 name: manifest.name.clone(),
512 version: Version::parse(&manifest.version)?,
513 dependencies: root_deps,
514 };
515
516 self.resolver.resolve(&root_package)
518 }
519
520 pub fn update(&mut self, package: Option<&str>) -> Result<()> {
522 let manifest_path = Path::new("package.pd");
524 let manifest = Self::load_manifest(manifest_path)?;
525
526 if let Some(pkg_name) = package {
527 println!("📦 Updating {}...", pkg_name);
528 } else {
529 println!("📦 Updating all dependencies...");
530 }
531
532 let resolved = self.resolve_dependencies(&manifest)?;
534
535 let lockfile_path = Path::new("package.lock");
537 if lockfile_path.exists() {
538 let old_lockfile = Lockfile::load(lockfile_path)?;
539 let mut new_lockfile = Lockfile::new(&manifest.name, &manifest.version);
540
541 for (package_name, version) in &resolved.packages {
543 if package_name == &manifest.name {
544 continue;
545 }
546
547 new_lockfile.add_package(LockedPackage {
548 name: package_name.clone(),
549 version: version.to_string(),
550 source: PackageSource::Registry {
551 url: self.registry_url.clone(),
552 },
553 dependencies: vec![], checksum: "TODO".to_string(), });
556 }
557
558 let diff = lockfile::LockfileDiff::compute(&old_lockfile, &new_lockfile);
560 println!("\n{}", diff.display());
561
562 new_lockfile.save(lockfile_path)?;
564 } else {
565 self.install()?;
567 }
568
569 Ok(())
570 }
571
572 pub fn manifest_to_string(manifest: &PackageManifest) -> String {
574 let mut result = String::new();
575
576 result.push_str(&format!("name = \"{}\"\n", manifest.name));
578 result.push_str(&format!("version = \"{}\"\n", manifest.version));
579
580 if let Some(desc) = &manifest.description {
581 result.push_str(&format!("description = \"{}\"\n", desc));
582 }
583
584 if !manifest.authors.is_empty() {
585 result.push_str("authors = [");
586 for (i, author) in manifest.authors.iter().enumerate() {
587 if i > 0 {
588 result.push_str(", ");
589 }
590 result.push_str(&format!("\"{}\"", author));
591 }
592 result.push_str("]\n");
593 }
594
595 if let Some(license) = &manifest.license {
596 result.push_str(&format!("license = \"{}\"\n", license));
597 }
598
599 if !manifest.dependencies.is_empty() {
601 result.push_str("\n[dependencies]\n");
602 for (name, dep) in &manifest.dependencies {
603 match dep {
604 Dependency::Version(v) => {
605 result.push_str(&format!("{} = \"{}\"\n", name, v));
606 }
607 Dependency::Detailed { .. } => {
608 result.push_str(&format!("{} = \"*\"\n", name));
610 }
611 }
612 }
613 }
614
615 if !manifest.dev_dependencies.is_empty() {
617 result.push_str("\n[dev-dependencies]\n");
618 for (name, dep) in &manifest.dev_dependencies {
619 match dep {
620 Dependency::Version(v) => {
621 result.push_str(&format!("{} = \"{}\"\n", name, v));
622 }
623 Dependency::Detailed { .. } => {
624 result.push_str(&format!("{} = \"*\"\n", name));
625 }
626 }
627 }
628 }
629
630 result
631 }
632
633 pub fn add_dependency(&mut self, name: &str, version: &str, dev: bool) -> Result<()> {
635 let manifest_path = Path::new("package.pd");
637 let mut manifest = Self::load_manifest(manifest_path)?;
638
639 let dep = Dependency::Version(version.to_string());
641 if dev {
642 manifest.dev_dependencies.insert(name.to_string(), dep);
643 println!("➕ Added dev dependency: {} = \"{}\"", name, version);
644 } else {
645 manifest.dependencies.insert(name.to_string(), dep);
646 println!("➕ Added dependency: {} = \"{}\"", name, version);
647 }
648
649 let content = Self::manifest_to_string(&manifest);
651 fs::write(manifest_path, content).map_err(CompileError::IoError)?;
652
653 Ok(())
654 }
655
656 pub fn build(&self, release: bool) -> Result<()> {
658 let manifest_path = Path::new("package.pd");
660 let manifest = Self::load_manifest(manifest_path)?;
661
662 println!("🔨 Building package '{}'...", manifest.name);
663
664 let entry = manifest.main.as_deref().unwrap_or("src/main.pd");
666 let entry_path = Path::new(entry);
667
668 if !entry_path.exists() {
669 return Err(CompileError::Generic(format!(
670 "Entry point '{}' not found",
671 entry
672 )));
673 }
674
675 let driver = crate::Driver::new();
677 let build_dir = Path::new("target").join(if release { "release" } else { "debug" });
681 if !build_dir.exists() {
682 fs::create_dir_all(&build_dir).map_err(CompileError::IoError)?;
683 }
684
685 let output = driver.compile_file(entry_path)?;
687
688 let target_name = format!("{}.c", manifest.name);
690 let target_path = build_dir.join(&target_name);
691 fs::rename(&output, &target_path).map_err(CompileError::IoError)?;
692
693 println!("✅ Build complete: {}", target_path.display());
694
695 Ok(())
696 }
697
698 pub fn run(&self, args: Vec<String>, release: bool) -> Result<()> {
700 self.build(release)?;
702
703 let manifest = Self::load_manifest(Path::new("package.pd"))?;
705
706 let build_dir = Path::new("target").join(if release { "release" } else { "debug" });
708 let c_file = build_dir.join(format!("{}.c", manifest.name));
709 let exe_file = build_dir.join(&manifest.name);
710
711 println!("🔗 Linking executable...");
713
714 let runtime_path = PathBuf::from("runtime/palladium_runtime.c");
716
717 let gcc_output = std::process::Command::new("gcc")
718 .arg(&c_file)
719 .arg(&runtime_path)
720 .arg("-o")
721 .arg(&exe_file)
722 .output()
723 .map_err(|e| CompileError::Generic(format!("Failed to run gcc: {}", e)))?;
724
725 if !gcc_output.status.success() {
726 let stderr = String::from_utf8_lossy(&gcc_output.stderr);
727 return Err(CompileError::Generic(format!(
728 "gcc compilation failed:\n{}",
729 stderr
730 )));
731 }
732
733 println!("🚀 Running '{}'...", manifest.name);
735 println!("─────────────────────────────────────");
736
737 let mut cmd = std::process::Command::new(&exe_file);
738 cmd.args(&args);
739
740 let status = cmd
741 .status()
742 .map_err(|e| CompileError::Generic(format!("Failed to run program: {}", e)))?;
743
744 println!("─────────────────────────────────────");
745
746 if !status.success() {
747 let exit_code = status.code().unwrap_or(-1);
748 println!("⚠️ Program exited with code: {}", exit_code);
749 } else {
750 println!("✅ Program completed successfully");
751 }
752
753 Ok(())
754 }
755}
756
757impl Default for PackageManager {
758 fn default() -> Self {
759 Self::new().expect("Failed to create package manager")
760 }
761}