use crate::package::{fetch_package_info, Package};
use crates_io_api::SyncClient;
use petgraph::dot::{Config, Dot};
use petgraph::graph::{DiGraph, NodeIndex};
use petgraph::visit::Dfs;
use std::collections::{HashMap, HashSet};
#[derive(Debug)]
pub struct DependencyGraph {
graph: DiGraph<(String, String), &'static str>,
}
impl Default for DependencyGraph {
fn default() -> Self {
Self::new()
}
}
impl DependencyGraph {
pub fn new() -> Self {
DependencyGraph {
graph: DiGraph::new(),
}
}
pub fn fetch_dependency_tree(
&mut self,
package_name: &str,
depth: usize,
optional: bool,
) -> Result<Option<Package>, Box<dyn std::error::Error>> {
let mut visited_packages = HashMap::new();
let client = SyncClient::new(
"my-user-agent (my-contact@domain.com)",
std::time::Duration::from_millis(1000),
)
.unwrap();
fetch_package_info(
&(package_name.to_string(), "".to_string()),
&mut visited_packages,
self,
&client,
depth,
optional,
)
}
pub fn add_package_to_graph(&mut self, package: &Package) -> NodeIndex {
let node_index = self
.graph
.add_node((package.name.clone(), package.url.clone()));
for dependency in &package.dependencies {
if !self
.graph
.node_indices()
.any(|i| self.graph[i] == *dependency)
{
let index = self.graph.add_node(dependency.clone());
self.add_dependency_edge(node_index, index);
}
}
node_index
}
pub fn add_dependency_edge(&mut self, source: NodeIndex, target: NodeIndex) {
self.graph.add_edge(source, target, "depends");
}
pub fn print_dependencies_at_level(&self, package: &Package, depth: usize, max_depth: usize) {
let mut visited_nodes = HashSet::new();
let mut printed_packages = HashSet::new();
self.print_dependencies_recursive(
package,
depth,
max_depth,
&mut visited_nodes,
&mut printed_packages,
);
}
pub fn print_dependencies_recursive(
&self,
package: &Package,
depth: usize,
max_depth: usize,
visited_nodes: &mut HashSet<NodeIndex>,
printed_packages: &mut HashSet<String>,
) {
if depth < max_depth {
let node_index = self
.graph
.node_indices()
.find(|&index| self.graph[index] == (package.name.clone(), package.url.clone()))
.unwrap_or_else(NodeIndex::end);
if node_index != NodeIndex::end() && visited_nodes.insert(node_index) {
let package_key = &package.name;
if printed_packages.insert(package_key.clone()) || max_depth > 2 {
let color_code = if depth % 2 == 0 { 32 } else { 37 };
println!(
"{:indent$}\x1b[{}m ├── {} - ({})\x1b[0m",
"",
color_code,
package.name,
package.url,
indent = depth * 3
);
let mut dfs = Dfs::new(&self.graph, node_index);
while let Some(neighbor_index) = dfs.next(&self.graph) {
let neighbor_package = Package::new(
self.graph[neighbor_index].clone().0,
self.graph[neighbor_index].clone().1,
vec![("".to_string(), "".to_string())],
false,
);
self.print_dependencies_recursive(
&neighbor_package,
depth + 1,
max_depth,
visited_nodes,
printed_packages,
);
}
}
}
}
}
pub fn to_dot(&self) -> String {
format!(
"{:?}",
Dot::with_config(&self.graph, &[Config::GraphContentOnly])
)
}
}