diff --git a/src/cli/help.rs b/src/cli/help.rs index 3601dbe..a3254d8 100644 --- a/src/cli/help.rs +++ b/src/cli/help.rs @@ -12,6 +12,7 @@ pub fn help() -> i32 { println!(" init Initialize the current directory as a project"); println!(" build [--profile] Build the project (default: --debug)"); println!(" run [--profile] Build and run the project (default: --debug)"); + println!(" tree Display the dependency tree"); println!(" test Run project tests (alias: tests)"); println!(" clean Remove the target directory"); println!(" gen Generate IDE integration files"); diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 6d9a93f..5ed19b4 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -10,3 +10,4 @@ pub mod new; pub mod run; pub mod setup; pub mod test; +pub mod tree; diff --git a/src/cli/tree.rs b/src/cli/tree.rs new file mode 100644 index 0000000..b38a9c4 --- /dev/null +++ b/src/cli/tree.rs @@ -0,0 +1,118 @@ +use crate::core::config::Config; +use crate::utils::log::error; +use crate::utils::text::{BOLD_CYAN, printc}; +use std::collections::HashSet; +use std::path::{Path, PathBuf}; +use toml::Value; + +pub fn tree(_args: &[String]) -> i32 { + let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); + let config = match Config::open("dcr.toml") { + Ok(c) => c, + Err(_) => { + error("dcr.toml not found in current directory"); + return 1; + } + }; + + let name = config + .get("package.name") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let version = config + .get("package.version") + .and_then(|v| v.as_str()) + .unwrap_or("0.0.0"); + + printc(&format!("{} v{}", name, version), BOLD_CYAN); + + let mut seen = HashSet::new(); + seen.insert(name.to_string()); + + if let Some(deps) = config.get("dependencies").and_then(|v| v.as_table()) { + print_deps(deps, ¤t_dir, "", &mut seen); + } + + 0 +} + +fn print_deps( + deps: &toml::value::Table, + base_path: &Path, + prefix: &str, + seen: &mut HashSet, +) { + let mut dep_list: Vec<_> = deps.iter().collect(); + dep_list.sort_by_key(|(name, _)| *name); + + for (i, (name, value)) in dep_list.iter().enumerate() { + let is_last = i == dep_list.len() - 1; + let connector = if is_last { "└── " } else { "├── " }; + let child_prefix = if is_last { " " } else { "│ " }; + + let dep_path = resolve_dep_path(value, base_path); + let mut version = String::new(); + let mut sub_deps = None; + let mut resolved_path = None; + + if let Some(path) = &dep_path { + let dcr_toml = path.join("dcr.toml"); + if dcr_toml.exists() + && let Ok(config) = Config::open(&dcr_toml.to_string_lossy()) + { + if let Some(v) = config.get("package.version").and_then(|v| v.as_str()) { + version = format!(" v{}", v); + } + sub_deps = config + .get("dependencies") + .and_then(|v| v.as_table()) + .cloned(); + resolved_path = Some(path.clone()); + } + } + + if version.is_empty() { + version = match value { + Value::String(s) if s.starts_with("git:") || s.starts_with("github:") => { + format!(" ({})", s) + } + Value::Table(t) => { + if let Some(v) = t.get("version").and_then(|v| v.as_str()) { + format!(" v{}", v) + } else if let Some(git) = t.get("git").and_then(|v| v.as_str()) { + format!(" ({})", git) + } else { + "".to_string() + } + } + _ => "".to_string(), + }; + } + + println!("{}{}{}{}", prefix, connector, name, version); + + if let Some(s_deps) = sub_deps + && !seen.contains(*name) + { + seen.insert(name.to_string()); + let new_prefix = format!("{}{}", prefix, child_prefix); + if let Some(path) = resolved_path { + print_deps(&s_deps, &path, &new_prefix, seen); + } + seen.remove(*name); + } + } +} + +fn resolve_dep_path(value: &Value, base_path: &Path) -> Option { + match value { + Value::String(s) => s + .strip_prefix("path:") + .map(|stripped| base_path.join(stripped)), + Value::Table(t) => t + .get("path") + .and_then(|v| v.as_str()) + .map(|path| base_path.join(path)), + _ => None, + } +} diff --git a/src/main.rs b/src/main.rs index cb83903..805763b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ fn main() { "add" => cli::add::add(rest), "build" => cli::build::build(rest), "run" => cli::run::run(rest), + "tree" => cli::tree::tree(rest), "test" | "tests" => cli::test::test(rest), "clean" => cli::clean::clean(rest), "gen" => cli::r#gen::r#gen(rest),