use crate::content::RecipeItem;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::path::Path;
use colored::Colorize;
const WIRE: &str = "│ ";
const CONNECTOR: &str = "├── ";
const CAP: &str = "└── ";
const GAP: &str = " ";
pub fn repr_tree(files: &[RecipeItem]) -> String {
let tree = build_recursive_content(files);
make_tree_string(&tree, "".into())
}
fn build_recursive_content(files: &[RecipeItem]) -> Vec<TreeContent> {
use RecipeItem::*;
let mut root = TreeNode::new();
for file in files {
match file {
File(file) => {
root.insert(file.name.as_path(), true);
}
Directory(name) => {
root.insert(name, false);
}
}
}
let mut out: Vec<_> = root
.children
.into_iter()
.map(|(name, node)| node.into_tree_content(name))
.collect();
out.sort_unstable();
out
}
fn make_tree_string(cont: &[TreeContent], prefix: String) -> String {
let mut out = String::new();
let mut rec_iter = cont.iter().peekable();
while let Some(file) = rec_iter.next() {
use TreeContent::*;
let is_last = rec_iter.peek().is_none();
let line = if is_last { CAP } else { CONNECTOR };
match file {
Leaf { name, empty_dir } => {
#[rustfmt::skip]
let name = if *empty_dir { name.blue() } else { name.normal() };
let new_line = format!(
"{}{}{}\n",
prefix.truecolor(128, 128, 128),
line.truecolor(128, 128, 128),
name
);
out.push_str(&new_line);
}
HasChildren { name, contents } => {
let new_line = format!(
"{}{}{}\n",
prefix.truecolor(128, 128, 128),
line.truecolor(128, 128, 128),
name.blue()
);
let new_prefix = format!("{}{}", prefix, if is_last { GAP } else { WIRE });
let rec = make_tree_string(contents, new_prefix);
out.push_str(&new_line);
out.push_str(&rec);
}
}
}
out
}
enum TreeContent {
Leaf {
name: String,
empty_dir: bool,
},
HasChildren {
name: String,
contents: Vec<TreeContent>,
},
}
struct TreeNode {
children: BTreeMap<String, TreeNode>,
is_file: bool,
}
impl TreeNode {
fn new() -> Self {
TreeNode {
children: BTreeMap::new(),
is_file: false,
}
}
fn insert(&mut self, path: &Path, is_file: bool) {
let mut current = self;
for comp in path.components() {
let name = comp.as_os_str().to_string_lossy().into_owned();
current = current.children.entry(name).or_insert_with(TreeNode::new)
}
if is_file {
current.is_file = true;
}
}
fn into_tree_content(self, name: String) -> TreeContent {
use TreeContent::*;
if self.children.is_empty() {
Leaf {
name,
empty_dir: !self.is_file,
}
} else {
let mut contents: Vec<_> = self
.children
.into_iter()
.map(|(name, node)| node.into_tree_content(name))
.collect();
contents.sort_unstable();
HasChildren {
name,
contents,
}
}
}
}
impl PartialEq for TreeContent {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl Eq for TreeContent {}
impl PartialOrd for TreeContent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TreeContent {
fn cmp(&self, other: &Self) -> Ordering {
use TreeContent::*;
fn key(t: &TreeContent) -> (bool, &str) {
match t {
Leaf { name, empty_dir } => (!*empty_dir, name.as_str()),
HasChildren { name, .. } => (false, name.as_str()),
}
}
key(self).cmp(&key(other))
}
}