feat: implement package manager
Adds dependency management for Lux projects: - `lux pkg install` - Install all dependencies from lux.toml - `lux pkg add <pkg>` - Add a dependency (supports --git, --path) - `lux pkg remove <pkg>` - Remove a dependency - `lux pkg list` - List dependencies with install status - `lux pkg update` - Update all dependencies - `lux pkg clean` - Remove installed packages Supports three dependency sources: - Registry (placeholder for future package registry) - Git repositories (with optional branch) - Local file paths Packages are installed to .lux_packages/ in the project root. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
156
src/main.rs
156
src/main.rs
@@ -9,6 +9,7 @@ mod interpreter;
|
||||
mod lexer;
|
||||
mod lsp;
|
||||
mod modules;
|
||||
mod package;
|
||||
mod parser;
|
||||
mod schema;
|
||||
mod typechecker;
|
||||
@@ -133,6 +134,10 @@ fn main() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"pkg" => {
|
||||
// Package manager
|
||||
handle_pkg_command(&args[2..]);
|
||||
}
|
||||
path => {
|
||||
// Run a file
|
||||
run_file(path);
|
||||
@@ -156,6 +161,7 @@ fn print_help() {
|
||||
println!(" lux watch <file.lux> Watch and re-run on changes");
|
||||
println!(" lux debug <file.lux> Start interactive debugger");
|
||||
println!(" lux init [name] Initialize a new project");
|
||||
println!(" lux pkg <command> Package manager (install, add, remove, list, update)");
|
||||
println!(" lux --lsp Start LSP server (for IDE integration)");
|
||||
println!(" lux --help Show this help");
|
||||
println!(" lux --version Show version");
|
||||
@@ -382,6 +388,156 @@ fn watch_file(path: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pkg_command(args: &[String]) {
|
||||
use package::{PackageManager, DependencySource};
|
||||
use std::path::PathBuf;
|
||||
|
||||
if args.is_empty() {
|
||||
print_pkg_help();
|
||||
return;
|
||||
}
|
||||
|
||||
// Help doesn't require being in a project
|
||||
if matches!(args[0].as_str(), "help" | "--help" | "-h") {
|
||||
print_pkg_help();
|
||||
return;
|
||||
}
|
||||
|
||||
let project_root = match PackageManager::find_project_root() {
|
||||
Some(root) => root,
|
||||
None => {
|
||||
eprintln!("Error: Not in a Lux project (no lux.toml found)");
|
||||
eprintln!("Run 'lux init' to create a new project");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let pkg = PackageManager::new(&project_root);
|
||||
|
||||
match args[0].as_str() {
|
||||
"install" | "i" => {
|
||||
if let Err(e) = pkg.install() {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"add" => {
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: lux pkg add <package> [version] [--git <url>] [--path <path>]");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let name = &args[1];
|
||||
let mut version = "0.0.0".to_string();
|
||||
let mut source = DependencySource::Registry;
|
||||
|
||||
let mut i = 2;
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--git" => {
|
||||
if i + 1 < args.len() {
|
||||
let url = args[i + 1].clone();
|
||||
let branch = if i + 3 < args.len() && args[i + 2] == "--branch" {
|
||||
i += 2;
|
||||
Some(args[i + 1].clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
source = DependencySource::Git { url, branch };
|
||||
i += 2;
|
||||
} else {
|
||||
eprintln!("--git requires a URL");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"--path" => {
|
||||
if i + 1 < args.len() {
|
||||
source = DependencySource::Path {
|
||||
path: PathBuf::from(&args[i + 1]),
|
||||
};
|
||||
i += 2;
|
||||
} else {
|
||||
eprintln!("--path requires a path");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
v if !v.starts_with('-') => {
|
||||
version = v.to_string();
|
||||
i += 1;
|
||||
}
|
||||
_ => i += 1,
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = pkg.add(name, &version, source) {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"remove" | "rm" => {
|
||||
if args.len() < 2 {
|
||||
eprintln!("Usage: lux pkg remove <package>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
if let Err(e) = pkg.remove(&args[1]) {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"list" | "ls" => {
|
||||
if let Err(e) = pkg.list() {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"update" => {
|
||||
if let Err(e) = pkg.update() {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"clean" => {
|
||||
if let Err(e) = pkg.clean() {
|
||||
eprintln!("Error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
"help" | "--help" | "-h" => {
|
||||
print_pkg_help();
|
||||
}
|
||||
unknown => {
|
||||
eprintln!("Unknown package command: {}", unknown);
|
||||
print_pkg_help();
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_pkg_help() {
|
||||
println!("Lux Package Manager");
|
||||
println!();
|
||||
println!("Usage: lux pkg <command> [options]");
|
||||
println!();
|
||||
println!("Commands:");
|
||||
println!(" install, i Install all dependencies from lux.toml");
|
||||
println!(" add <pkg> Add a dependency");
|
||||
println!(" Options:");
|
||||
println!(" [version] Specify version (default: 0.0.0)");
|
||||
println!(" --git <url> Install from git repository");
|
||||
println!(" --branch <name> Git branch (with --git)");
|
||||
println!(" --path <path> Install from local path");
|
||||
println!(" remove, rm Remove a dependency");
|
||||
println!(" list, ls List dependencies and their status");
|
||||
println!(" update Update all dependencies");
|
||||
println!(" clean Remove installed packages");
|
||||
println!();
|
||||
println!("Examples:");
|
||||
println!(" lux pkg install");
|
||||
println!(" lux pkg add http 1.0.0");
|
||||
println!(" lux pkg add mylib --git https://github.com/user/mylib");
|
||||
println!(" lux pkg add local-lib --path ../lib");
|
||||
println!(" lux pkg remove http");
|
||||
}
|
||||
|
||||
fn init_project(name: Option<&str>) {
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
621
src/package.rs
Normal file
621
src/package.rs
Normal file
@@ -0,0 +1,621 @@
|
||||
//! Package manager for Lux
|
||||
//!
|
||||
//! Handles dependency management, package installation, and version resolution.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// Package manifest (lux.toml)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Manifest {
|
||||
pub project: ProjectInfo,
|
||||
pub dependencies: HashMap<String, Dependency>,
|
||||
}
|
||||
|
||||
/// Project information from lux.toml
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProjectInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub authors: Vec<String>,
|
||||
pub license: Option<String>,
|
||||
}
|
||||
|
||||
/// A package dependency
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dependency {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub source: DependencySource,
|
||||
}
|
||||
|
||||
/// Source of a dependency
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DependencySource {
|
||||
/// From the Lux package registry
|
||||
Registry,
|
||||
/// From a git repository
|
||||
Git { url: String, branch: Option<String> },
|
||||
/// From a local path
|
||||
Path { path: PathBuf },
|
||||
}
|
||||
|
||||
/// Package manager
|
||||
pub struct PackageManager {
|
||||
/// Root directory of the project
|
||||
project_root: PathBuf,
|
||||
/// Directory where packages are cached
|
||||
cache_dir: PathBuf,
|
||||
/// Directory where packages are installed (project-local)
|
||||
packages_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl PackageManager {
|
||||
pub fn new(project_root: &Path) -> Self {
|
||||
let cache_dir = dirs::cache_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join("lux")
|
||||
.join("packages");
|
||||
|
||||
let packages_dir = project_root.join(".lux_packages");
|
||||
|
||||
Self {
|
||||
project_root: project_root.to_path_buf(),
|
||||
cache_dir,
|
||||
packages_dir,
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the project root by looking for lux.toml
|
||||
pub fn find_project_root() -> Option<PathBuf> {
|
||||
let mut current = std::env::current_dir().ok()?;
|
||||
|
||||
loop {
|
||||
if current.join("lux.toml").exists() {
|
||||
return Some(current);
|
||||
}
|
||||
|
||||
if !current.pop() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the manifest from lux.toml
|
||||
pub fn load_manifest(&self) -> Result<Manifest, String> {
|
||||
let manifest_path = self.project_root.join("lux.toml");
|
||||
|
||||
if !manifest_path.exists() {
|
||||
return Err("No lux.toml found in project root".to_string());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&manifest_path)
|
||||
.map_err(|e| format!("Failed to read lux.toml: {}", e))?;
|
||||
|
||||
parse_manifest(&content)
|
||||
}
|
||||
|
||||
/// Save the manifest to lux.toml
|
||||
pub fn save_manifest(&self, manifest: &Manifest) -> Result<(), String> {
|
||||
let manifest_path = self.project_root.join("lux.toml");
|
||||
let content = format_manifest(manifest);
|
||||
|
||||
fs::write(&manifest_path, content)
|
||||
.map_err(|e| format!("Failed to write lux.toml: {}", e))
|
||||
}
|
||||
|
||||
/// Add a dependency to the project
|
||||
pub fn add(&self, name: &str, version: &str, source: DependencySource) -> Result<(), String> {
|
||||
let mut manifest = self.load_manifest()?;
|
||||
|
||||
let dep = Dependency {
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
source,
|
||||
};
|
||||
|
||||
manifest.dependencies.insert(name.to_string(), dep);
|
||||
self.save_manifest(&manifest)?;
|
||||
|
||||
println!("Added {} v{} to dependencies", name, version);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a dependency from the project
|
||||
pub fn remove(&self, name: &str) -> Result<(), String> {
|
||||
let mut manifest = self.load_manifest()?;
|
||||
|
||||
if manifest.dependencies.remove(name).is_some() {
|
||||
self.save_manifest(&manifest)?;
|
||||
|
||||
// Remove from packages dir if it exists
|
||||
let pkg_dir = self.packages_dir.join(name);
|
||||
if pkg_dir.exists() {
|
||||
fs::remove_dir_all(&pkg_dir)
|
||||
.map_err(|e| format!("Failed to remove package directory: {}", e))?;
|
||||
}
|
||||
|
||||
println!("Removed {} from dependencies", name);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Dependency '{}' not found", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Install all dependencies
|
||||
pub fn install(&self) -> Result<(), String> {
|
||||
let manifest = self.load_manifest()?;
|
||||
|
||||
if manifest.dependencies.is_empty() {
|
||||
println!("No dependencies to install.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create packages directory
|
||||
fs::create_dir_all(&self.packages_dir)
|
||||
.map_err(|e| format!("Failed to create packages directory: {}", e))?;
|
||||
|
||||
println!("Installing {} dependencies...", manifest.dependencies.len());
|
||||
println!();
|
||||
|
||||
for (name, dep) in &manifest.dependencies {
|
||||
self.install_dependency(dep)?;
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("Done! Installed {} packages.", manifest.dependencies.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a single dependency
|
||||
fn install_dependency(&self, dep: &Dependency) -> Result<(), String> {
|
||||
print!(" Installing {} v{}... ", dep.name, dep.version);
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let dest_dir = self.packages_dir.join(&dep.name);
|
||||
|
||||
match &dep.source {
|
||||
DependencySource::Registry => {
|
||||
// For now, simulate registry installation
|
||||
// In a real implementation, this would fetch from a package registry
|
||||
self.install_from_registry(dep, &dest_dir)?;
|
||||
}
|
||||
DependencySource::Git { url, branch } => {
|
||||
self.install_from_git(url, branch.as_deref(), &dest_dir)?;
|
||||
}
|
||||
DependencySource::Path { path } => {
|
||||
self.install_from_path(path, &dest_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
println!("done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_from_registry(&self, dep: &Dependency, dest: &Path) -> Result<(), String> {
|
||||
// Check if already installed with correct version
|
||||
let version_file = dest.join(".version");
|
||||
if version_file.exists() {
|
||||
let installed_version = fs::read_to_string(&version_file).unwrap_or_default();
|
||||
if installed_version.trim() == dep.version {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
let cache_path = self.cache_dir.join(&dep.name).join(&dep.version);
|
||||
|
||||
if cache_path.exists() {
|
||||
// Copy from cache
|
||||
copy_dir_recursive(&cache_path, dest)?;
|
||||
} else {
|
||||
// Create placeholder package (in real impl, would download)
|
||||
fs::create_dir_all(dest)
|
||||
.map_err(|e| format!("Failed to create package directory: {}", e))?;
|
||||
|
||||
// Create a lib.lux placeholder
|
||||
let lib_content = format!(
|
||||
"// Package: {} v{}\n// This is a placeholder - real package would be downloaded from registry\n\n",
|
||||
dep.name, dep.version
|
||||
);
|
||||
fs::write(dest.join("lib.lux"), lib_content)
|
||||
.map_err(|e| format!("Failed to create lib.lux: {}", e))?;
|
||||
}
|
||||
|
||||
// Write version file
|
||||
fs::write(&version_file, &dep.version)
|
||||
.map_err(|e| format!("Failed to write version file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_from_git(&self, url: &str, branch: Option<&str>, dest: &Path) -> Result<(), String> {
|
||||
// Remove existing if present
|
||||
if dest.exists() {
|
||||
fs::remove_dir_all(dest)
|
||||
.map_err(|e| format!("Failed to remove existing directory: {}", e))?;
|
||||
}
|
||||
|
||||
// Clone the repository
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
cmd.arg("clone")
|
||||
.arg("--depth").arg("1");
|
||||
|
||||
if let Some(b) = branch {
|
||||
cmd.arg("--branch").arg(b);
|
||||
}
|
||||
|
||||
cmd.arg(url).arg(dest);
|
||||
|
||||
let output = cmd.output()
|
||||
.map_err(|e| format!("Failed to run git: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(format!(
|
||||
"Git clone failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
// Remove .git directory to save space
|
||||
let git_dir = dest.join(".git");
|
||||
if git_dir.exists() {
|
||||
fs::remove_dir_all(&git_dir).ok();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_from_path(&self, source: &Path, dest: &Path) -> Result<(), String> {
|
||||
let source = if source.is_absolute() {
|
||||
source.to_path_buf()
|
||||
} else {
|
||||
self.project_root.join(source)
|
||||
};
|
||||
|
||||
if !source.exists() {
|
||||
return Err(format!("Source path does not exist: {}", source.display()));
|
||||
}
|
||||
|
||||
// Remove existing if present
|
||||
if dest.exists() {
|
||||
fs::remove_dir_all(dest)
|
||||
.map_err(|e| format!("Failed to remove existing directory: {}", e))?;
|
||||
}
|
||||
|
||||
// Copy the directory
|
||||
copy_dir_recursive(&source, dest)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List installed packages
|
||||
pub fn list(&self) -> Result<(), String> {
|
||||
let manifest = self.load_manifest()?;
|
||||
|
||||
if manifest.dependencies.is_empty() {
|
||||
println!("No dependencies in lux.toml");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Dependencies:");
|
||||
for (name, dep) in &manifest.dependencies {
|
||||
let source_info = match &dep.source {
|
||||
DependencySource::Registry => "registry".to_string(),
|
||||
DependencySource::Git { url, branch } => {
|
||||
if let Some(b) = branch {
|
||||
format!("git: {} ({})", url, b)
|
||||
} else {
|
||||
format!("git: {}", url)
|
||||
}
|
||||
}
|
||||
DependencySource::Path { path } => format!("path: {}", path.display()),
|
||||
};
|
||||
|
||||
let installed = self.packages_dir.join(name).exists();
|
||||
let status = if installed { "✓" } else { "✗" };
|
||||
|
||||
println!(" {} {} v{} [{}]", status, name, dep.version, source_info);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update all dependencies
|
||||
pub fn update(&self) -> Result<(), String> {
|
||||
let manifest = self.load_manifest()?;
|
||||
|
||||
if manifest.dependencies.is_empty() {
|
||||
println!("No dependencies to update.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Updating {} dependencies...", manifest.dependencies.len());
|
||||
println!();
|
||||
|
||||
for (name, dep) in &manifest.dependencies {
|
||||
// Force reinstall
|
||||
let dest_dir = self.packages_dir.join(name);
|
||||
if dest_dir.exists() {
|
||||
fs::remove_dir_all(&dest_dir).ok();
|
||||
}
|
||||
self.install_dependency(dep)?;
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("Done!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the search paths for installed packages
|
||||
pub fn get_package_paths(&self) -> Vec<PathBuf> {
|
||||
let mut paths = vec![];
|
||||
|
||||
if self.packages_dir.exists() {
|
||||
paths.push(self.packages_dir.clone());
|
||||
}
|
||||
|
||||
// Also include global cache
|
||||
if self.cache_dir.exists() {
|
||||
paths.push(self.cache_dir.clone());
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
/// Clean the packages directory
|
||||
pub fn clean(&self) -> Result<(), String> {
|
||||
if self.packages_dir.exists() {
|
||||
fs::remove_dir_all(&self.packages_dir)
|
||||
.map_err(|e| format!("Failed to remove packages directory: {}", e))?;
|
||||
println!("Cleaned .lux_packages directory");
|
||||
} else {
|
||||
println!("Nothing to clean");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a lux.toml manifest
|
||||
fn parse_manifest(content: &str) -> Result<Manifest, String> {
|
||||
let mut project = ProjectInfo {
|
||||
name: String::new(),
|
||||
version: String::new(),
|
||||
description: None,
|
||||
authors: vec![],
|
||||
license: None,
|
||||
};
|
||||
let mut dependencies = HashMap::new();
|
||||
|
||||
let mut current_section = "";
|
||||
|
||||
for line in content.lines() {
|
||||
let line = line.trim();
|
||||
|
||||
// Skip comments and empty lines
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Section header
|
||||
if line.starts_with('[') && line.ends_with(']') {
|
||||
current_section = &line[1..line.len()-1];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Key = value
|
||||
if let Some(eq_pos) = line.find('=') {
|
||||
let key = line[..eq_pos].trim();
|
||||
let value = line[eq_pos+1..].trim();
|
||||
let value = value.trim_matches('"');
|
||||
|
||||
match current_section {
|
||||
"project" => {
|
||||
match key {
|
||||
"name" => project.name = value.to_string(),
|
||||
"version" => project.version = value.to_string(),
|
||||
"description" => project.description = Some(value.to_string()),
|
||||
"license" => project.license = Some(value.to_string()),
|
||||
"authors" => {
|
||||
// Simple array parsing
|
||||
let authors_str = value.trim_matches(|c| c == '[' || c == ']');
|
||||
project.authors = authors_str
|
||||
.split(',')
|
||||
.map(|s| s.trim().trim_matches('"').to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
"dependencies" => {
|
||||
// Parse dependency
|
||||
let dep = parse_dependency(key, value)?;
|
||||
dependencies.insert(key.to_string(), dep);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if project.name.is_empty() {
|
||||
return Err("Missing project name in lux.toml".to_string());
|
||||
}
|
||||
if project.version.is_empty() {
|
||||
return Err("Missing project version in lux.toml".to_string());
|
||||
}
|
||||
|
||||
Ok(Manifest { project, dependencies })
|
||||
}
|
||||
|
||||
fn parse_dependency(name: &str, value: &str) -> Result<Dependency, String> {
|
||||
let value = value.trim();
|
||||
|
||||
// Simple version string: "1.0.0"
|
||||
if value.starts_with('"') || value.chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) {
|
||||
let version = value.trim_matches('"');
|
||||
return Ok(Dependency {
|
||||
name: name.to_string(),
|
||||
version: version.to_string(),
|
||||
source: DependencySource::Registry,
|
||||
});
|
||||
}
|
||||
|
||||
// Inline table: { version = "1.0", git = "..." }
|
||||
if value.starts_with('{') && value.ends_with('}') {
|
||||
let inner = &value[1..value.len()-1];
|
||||
let mut version = "0.0.0".to_string();
|
||||
let mut git_url = None;
|
||||
let mut git_branch = None;
|
||||
let mut path = None;
|
||||
|
||||
for part in inner.split(',') {
|
||||
let part = part.trim();
|
||||
if let Some(eq_pos) = part.find('=') {
|
||||
let k = part[..eq_pos].trim();
|
||||
let v = part[eq_pos+1..].trim().trim_matches('"');
|
||||
|
||||
match k {
|
||||
"version" => version = v.to_string(),
|
||||
"git" => git_url = Some(v.to_string()),
|
||||
"branch" => git_branch = Some(v.to_string()),
|
||||
"path" => path = Some(PathBuf::from(v)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source = if let Some(url) = git_url {
|
||||
DependencySource::Git { url, branch: git_branch }
|
||||
} else if let Some(p) = path {
|
||||
DependencySource::Path { path: p }
|
||||
} else {
|
||||
DependencySource::Registry
|
||||
};
|
||||
|
||||
return Ok(Dependency {
|
||||
name: name.to_string(),
|
||||
version,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
Err(format!("Invalid dependency format for {}: {}", name, value))
|
||||
}
|
||||
|
||||
/// Format a manifest as TOML
|
||||
fn format_manifest(manifest: &Manifest) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
output.push_str("[project]\n");
|
||||
output.push_str(&format!("name = \"{}\"\n", manifest.project.name));
|
||||
output.push_str(&format!("version = \"{}\"\n", manifest.project.version));
|
||||
|
||||
if let Some(desc) = &manifest.project.description {
|
||||
output.push_str(&format!("description = \"{}\"\n", desc));
|
||||
}
|
||||
|
||||
if !manifest.project.authors.is_empty() {
|
||||
let authors: Vec<String> = manifest.project.authors.iter()
|
||||
.map(|a| format!("\"{}\"", a))
|
||||
.collect();
|
||||
output.push_str(&format!("authors = [{}]\n", authors.join(", ")));
|
||||
}
|
||||
|
||||
if let Some(license) = &manifest.project.license {
|
||||
output.push_str(&format!("license = \"{}\"\n", license));
|
||||
}
|
||||
|
||||
output.push_str("\n[dependencies]\n");
|
||||
|
||||
for (name, dep) in &manifest.dependencies {
|
||||
match &dep.source {
|
||||
DependencySource::Registry => {
|
||||
output.push_str(&format!("{} = \"{}\"\n", name, dep.version));
|
||||
}
|
||||
DependencySource::Git { url, branch } => {
|
||||
if let Some(b) = branch {
|
||||
output.push_str(&format!(
|
||||
"{} = {{ version = \"{}\", git = \"{}\", branch = \"{}\" }}\n",
|
||||
name, dep.version, url, b
|
||||
));
|
||||
} else {
|
||||
output.push_str(&format!(
|
||||
"{} = {{ version = \"{}\", git = \"{}\" }}\n",
|
||||
name, dep.version, url
|
||||
));
|
||||
}
|
||||
}
|
||||
DependencySource::Path { path } => {
|
||||
output.push_str(&format!(
|
||||
"{} = {{ version = \"{}\", path = \"{}\" }}\n",
|
||||
name, dep.version, path.display()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Recursively copy a directory
|
||||
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), String> {
|
||||
fs::create_dir_all(dst)
|
||||
.map_err(|e| format!("Failed to create directory: {}", e))?;
|
||||
|
||||
for entry in fs::read_dir(src).map_err(|e| format!("Failed to read directory: {}", e))? {
|
||||
let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?;
|
||||
let path = entry.path();
|
||||
let dest_path = dst.join(entry.file_name());
|
||||
|
||||
if path.is_dir() {
|
||||
copy_dir_recursive(&path, &dest_path)?;
|
||||
} else {
|
||||
fs::copy(&path, &dest_path)
|
||||
.map_err(|e| format!("Failed to copy file: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Platform-specific cache directory handling
|
||||
mod dirs {
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn cache_dir() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
std::env::var("XDG_CACHE_HOME")
|
||||
.map(PathBuf::from)
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
std::env::var("HOME")
|
||||
.map(|h| PathBuf::from(h).join(".cache"))
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
std::env::var("HOME")
|
||||
.map(|h| PathBuf::from(h).join("Library").join("Caches"))
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
std::env::var("LOCALAPPDATA")
|
||||
.map(PathBuf::from)
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user