use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use crate::types::project_structure::ProjectStructure;
pub struct ProjectStructureFormatter;
impl ProjectStructureFormatter {
pub fn format_as_tree(structure: &ProjectStructure) -> String {
let mut result = format!(
"### 项目结构信息\n项目名称: {}\n根目录: {}\n\n项目目录结构:\n```\n",
structure.project_name,
structure.root_path.to_string_lossy()
);
let mut tree = PathTree::new();
for file in &structure.files {
let normalized_path = Self::normalize_path(&file.path);
tree.insert_file(&normalized_path);
}
let tree_output = tree.to_tree_string();
result.push_str(&tree_output);
result.push_str("```\n");
result
}
fn normalize_path(path: &Path) -> PathBuf {
let path_str = path.to_string_lossy();
if path_str.starts_with("./") {
PathBuf::from(&path_str[2..])
} else {
path.to_path_buf()
}
}
}
#[derive(Debug)]
struct PathNode {
name: String,
children: BTreeMap<String, PathNode>,
}
impl PathNode {
fn new(name: String) -> Self {
Self {
name,
children: BTreeMap::new(),
}
}
}
#[derive(Debug)]
struct PathTree {
root: PathNode,
}
impl PathTree {
fn new() -> Self {
Self {
root: PathNode::new("".to_string()),
}
}
fn insert_file(&mut self, path: &Path) {
self.insert_path(path);
}
fn insert_path(&mut self, path: &Path) {
let components: Vec<&str> = path
.components()
.filter_map(|c| c.as_os_str().to_str())
.collect();
if components.is_empty() {
return;
}
let mut current = &mut self.root;
for (_i, component) in components.iter().enumerate() {
current
.children
.entry(component.to_string())
.or_insert_with(|| PathNode::new(component.to_string()));
current = current.children.get_mut(*component).unwrap();
}
}
fn to_tree_string(&self) -> String {
let mut result = String::new();
self.render_node(&self.root, "", true, &mut result);
result
}
fn render_node(&self, node: &PathNode, prefix: &str, is_last: bool, result: &mut String) {
if !node.name.is_empty() {
let connector = if is_last { "└── " } else { "├── " };
result.push_str(&format!("{}{}{}\n", prefix, connector, node.name));
}
let children: Vec<_> = node.children.values().collect();
for (i, child) in children.iter().enumerate() {
let is_last_child = i == children.len() - 1;
let new_prefix = if node.name.is_empty() {
prefix.to_string()
} else if is_last {
format!("{} ", prefix)
} else {
format!("{}│ ", prefix)
};
self.render_node(child, &new_prefix, is_last_child, result);
}
}
}