use indicatif::ProgressBar;
use std::collections::{HashMap, HashSet};
use std::{fs, time::Duration};
use crate::constants::others::SPINNER_UPDATE_DURATION;
use super::components::{MyComponent, ResolvedComponent};
pub struct Dependencies {}
impl Dependencies {
pub fn all_tree_resolved(
user_components: Vec<String>,
vec_components_from_index: &[MyComponent],
) -> HashMap<String, ResolvedComponent> {
let component_map: HashMap<String, MyComponent> = vec_components_from_index
.iter()
.map(|c| (c.name.clone(), c.clone()))
.collect();
resolve_all_dependencies(&component_map, &user_components).expect("Failed to resolve all dependencies")
}
pub fn get_all_resolved_components(resolved: &HashMap<String, ResolvedComponent>) -> Vec<String> {
let mut all_components = HashSet::new();
for name in resolved.keys() {
all_components.insert(name.clone());
}
for component in resolved.values() {
for dep in &component.resolved_registry_dependencies {
all_components.insert(dep.clone());
}
}
let mut result: Vec<String> = all_components.into_iter().collect();
result.sort();
result
}
pub fn get_all_resolved_parent_dirs(resolved: &HashMap<String, ResolvedComponent>) -> Vec<String> {
let mut all_parent_dirs = HashSet::new();
for component in resolved.values() {
all_parent_dirs.insert(component.component.parent_dir.clone());
}
let mut result: Vec<String> = all_parent_dirs.into_iter().collect();
result.sort();
result
}
pub fn get_all_resolved_cargo_dependencies(resolved: &HashMap<String, ResolvedComponent>) -> Vec<String> {
let mut all_cargo_deps = HashSet::new();
for component in resolved.values() {
for dep in &component.resolved_cargo_dependencies {
all_cargo_deps.insert(dep.clone());
}
}
let mut result: Vec<String> = all_cargo_deps.into_iter().collect();
result.sort();
result
}
pub fn print_dependency_tree(resolved: &HashMap<String, ResolvedComponent>) {
println!("Dependency Tree Resolution:");
let mut dependent_components = HashSet::new();
for resolved_comp in resolved.values() {
for dep in &resolved_comp.resolved_registry_dependencies {
dependent_components.insert(dep.clone());
}
}
for name in resolved.keys() {
if !dependent_components.contains(name) {
print_component_tree(name, resolved, resolved, 0);
}
}
}
pub fn add_cargo_dep_to_toml(cargo_deps: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let cargo_toml_path = find_cargo_toml()?;
let spinner = ProgressBar::new_spinner();
spinner.set_message("Adding crates to Cargo.toml...");
spinner.enable_steady_tick(Duration::from_millis(SPINNER_UPDATE_DURATION));
let mut cargo_toml_content = fs::read_to_string(&cargo_toml_path)?;
if !cargo_toml_content.contains("[dependencies]") {
cargo_toml_content.push_str("\n[dependencies]\n");
}
let mut added_deps = Vec::new();
for dep in cargo_deps {
if dep == "std" {
continue;
}
spinner.set_message(format!("📦 Adding crate: {dep}"));
let output = std::process::Command::new("cargo").arg("add").arg(dep).output()?;
if output.status.success() {
added_deps.push(dep);
} else {
eprintln!(
"Failed to add dependency {}: {}",
dep,
String::from_utf8_lossy(&output.stderr)
);
}
}
if !added_deps.is_empty() {
let dependencies_str = added_deps
.iter()
.map(|dep| dep.as_str())
.collect::<Vec<&str>>()
.join(", ");
let finish_message = format!("✔️ Successfully added to Cargo.toml: [{dependencies_str}] !");
spinner.finish_with_message(finish_message);
} else {
spinner.finish_with_message("No new crates to add");
}
Ok(())
}
}
fn resolve_all_dependencies(
component_map: &HashMap<String, MyComponent>,
user_components: &[String],
) -> Result<HashMap<String, ResolvedComponent>, Box<dyn std::error::Error>> {
let mut resolved_components: HashMap<String, ResolvedComponent> = HashMap::new();
for component_name in user_components {
if !component_map.contains_key(component_name) {
println!("🔸Component not found in registry: {component_name}");
continue;
}
resolve_component_recursive(
component_name,
component_map,
&mut resolved_components,
&mut HashSet::new(),
)?;
}
Ok(resolved_components)
}
fn resolve_component_recursive(
component_name: &str,
component_map: &HashMap<String, MyComponent>,
resolved_components: &mut HashMap<String, ResolvedComponent>,
visited: &mut HashSet<String>,
) -> Result<(HashSet<String>, HashSet<String>), Box<dyn std::error::Error>> {
if let Some(resolved) = resolved_components.get(component_name) {
return Ok((
resolved.resolved_registry_dependencies.clone(),
resolved.resolved_cargo_dependencies.clone(),
));
}
if !visited.insert(component_name.to_string()) {
return Err(format!("Circular dependency detected involving '{component_name}'").into());
}
let component = match component_map.get(component_name) {
Some(c) => c,
None => return Err(format!("Component '{component_name}' not found").into()),
};
let mut resolved_registry_dependencies = HashSet::new();
let mut resolved_cargo_dependencies = HashSet::new();
for cargo_dep in &component.cargo_dependencies {
resolved_cargo_dependencies.insert(cargo_dep.clone());
}
for dep_name in &component.registry_dependencies {
resolved_registry_dependencies.insert(dep_name.clone());
let (transitive_registry_deps, transitive_cargo_deps) =
resolve_component_recursive(dep_name, component_map, resolved_components, visited)?;
for trans_dep in transitive_registry_deps {
resolved_registry_dependencies.insert(trans_dep);
}
for cargo_dep in transitive_cargo_deps {
resolved_cargo_dependencies.insert(cargo_dep);
}
}
visited.remove(component_name);
resolved_components.insert(
component_name.to_string(),
ResolvedComponent {
component: component.clone(),
resolved_registry_dependencies: resolved_registry_dependencies.clone(),
resolved_cargo_dependencies: resolved_cargo_dependencies.clone(),
},
);
Ok((resolved_registry_dependencies, resolved_cargo_dependencies))
}
fn print_component_tree(
component_name: &str,
all_resolved: &HashMap<String, ResolvedComponent>,
current_branch: &HashMap<String, ResolvedComponent>,
depth: usize,
) {
if let Some(component) = current_branch.get(component_name) {
let indent = " ".repeat(depth);
println!("{}└─ {} ({})", indent, component_name, component.component.parent_dir);
let filtered_cargo_deps: Vec<&String> = component
.component
.cargo_dependencies
.iter()
.filter(|&dep| dep != "std")
.collect();
if !filtered_cargo_deps.is_empty() {
let cargo_indent = " ".repeat(depth + 1);
println!("{cargo_indent}└─ Cargo Dependencies:");
let mut cargo_deps = filtered_cargo_deps;
cargo_deps.sort();
for cargo_dep in cargo_deps {
let cargo_dep_indent = " ".repeat(depth + 2);
println!("{cargo_dep_indent}└─ {cargo_dep}");
}
}
let mut deps: Vec<&String> = component.component.registry_dependencies.iter().collect();
deps.sort();
for dep_name in deps {
if all_resolved.contains_key(dep_name) {
print_component_tree(dep_name, all_resolved, all_resolved, depth + 1);
} else {
let indent = " ".repeat(depth + 1);
println!("{indent}└─ {dep_name} (external)");
}
}
}
}
fn find_cargo_toml() -> Result<String, Box<dyn std::error::Error>> {
let mut current_dir = std::env::current_dir()?;
loop {
let cargo_toml_path = current_dir.join("Cargo.toml");
if cargo_toml_path.exists() {
return Ok(cargo_toml_path.to_string_lossy().to_string());
}
if !current_dir.pop() {
break;
}
}
Err("Could not find Cargo.toml in the current directory or any parent directories".into())
}