use core::fmt;
#[cfg(feature = "color")]
use colored::{ColoredString, Colorize};
use serde_yaml::Value;
use uuid::Uuid;
use crate::{errors::Error, filetree::Filetree};
const TAB_NODE_BELOW: &str = "│ ";
const TAB_NO_NODE_BELOW: &str = " ";
const NODE_ARM_LAST: &str = "└── ";
const NODE_ARM: &str = "├── ";
#[derive(Debug, Clone, PartialEq, Eq)]
struct NodeName(String);
#[cfg(feature = "color")]
impl NodeName {
fn colored(&self, is_directory: bool) -> ColoredString {
if is_directory {
self.0.cyan().bold()
} else {
self.0.clone().into()
}
}
}
impl From<NodeName> for String {
fn from(value: NodeName) -> Self {
value.0
}
}
impl fmt::Display for NodeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
uuid: Uuid,
name: NodeName,
children: Vec<Node>,
is_directory: bool,
}
impl Node {
pub fn new(name: String, children: Vec<Node>, is_directory: bool) -> Node {
Node {
uuid: Uuid::new_v4(),
name: NodeName(name),
children,
is_directory,
}
}
pub fn parent<'a>(&'a self, filetree: &'a Filetree) -> Option<&Node> {
filetree.root().get_parent_of(self)
}
pub(crate) fn add_children(&mut self, children: &mut Vec<Node>) {
self.children.append(children);
}
pub fn has_child(&self, child: &Node) -> bool {
self.children.contains(child)
}
pub fn get_parent_of(&self, child: &Node) -> Option<&Node> {
if self.has_child(child) {
Some(self)
} else {
for node in self.children.iter() {
if let Some(parent) = node.get_parent_of(child) {
return Some(parent);
}
}
None
}
}
fn next_sibling<'a>(&'a self, filetree: &'a Filetree) -> Option<&Node> {
let parent = self.parent(filetree)?;
let self_position = parent.children.iter().position(|child| child == self)?;
parent.children.get(self_position + 1)
}
fn next_pibling<'a>(&'a self, filetree: &'a Filetree) -> Option<&Node> {
self.parent(filetree)?.next_sibling(filetree)
}
fn modify_parts_from_parent<'a, T>(
&self,
parent_parts: &mut Vec<T>,
parent: &Node,
filetree: &Filetree,
) where
T: From<&'a str>,
{
if parent_parts.len() >= 2 {
parent_parts.drain(parent_parts.len() - 2..);
} else if !parent_parts.is_empty() {
parent_parts.drain(parent_parts.len() - 1..);
}
if self.next_pibling(filetree).is_some() {
parent_parts.push(TAB_NODE_BELOW.into());
} else if parent.parent(filetree).is_some() {
parent_parts.push(TAB_NO_NODE_BELOW.into());
}
if self.next_sibling(filetree).is_some() {
parent_parts.push(NODE_ARM.into());
} else {
parent_parts.push(NODE_ARM_LAST.into());
}
}
fn display_parts<'a>(&'a self, filetree: &'a Filetree) -> Vec<String> {
let Some(parent) = self.parent(filetree) else {
return vec![self.name.to_string()];
};
let mut parts = parent.display_parts(filetree);
self.modify_parts_from_parent(&mut parts, parent, filetree);
parts.push(self.name.to_string());
parts
}
#[cfg(feature = "color")]
fn display_parts_color<'a>(&'a self, filetree: &'a Filetree) -> Vec<ColoredString> {
let Some(parent) = self.parent(filetree) else {
return vec![self.name.colored(self.is_directory)];
};
let mut parts = parent.display_parts_color(filetree);
self.modify_parts_from_parent(&mut parts, parent, filetree);
parts.push(self.name.colored(self.is_directory));
parts
}
pub(crate) fn nodes(&self) -> Vec<&Node> {
let mut nodes = vec![self];
for child in self.children.iter().flat_map(|child| child.nodes()) {
nodes.push(child);
}
nodes
}
pub(crate) fn treeline(&self, filetree: &Filetree) -> String {
self.display_parts(filetree).join("")
}
#[cfg(feature = "color")]
pub(crate) fn treeline_color(&self, filetree: &Filetree) -> String {
self.display_parts_color(filetree)
.iter()
.map(ToString::to_string)
.collect()
}
}
pub(crate) struct Nodes(pub Vec<Node>);
impl From<Vec<Node>> for Nodes {
fn from(value: Vec<Node>) -> Self {
Nodes(value)
}
}
impl From<String> for Nodes {
fn from(value: String) -> Self {
vec![Node::new(value.clone(), vec![], false)].into()
}
}
impl TryFrom<Vec<Value>> for Nodes {
type Error = Error;
fn try_from(value: Vec<Value>) -> Result<Self, Self::Error> {
Ok(value
.into_iter()
.map(|v| v.try_into())
.collect::<Result<Vec<Nodes>, Error>>()?
.into_iter()
.flat_map(|nodes| nodes.0)
.collect::<Vec<_>>()
.into())
}
}
impl TryFrom<serde_yaml::Mapping> for Nodes {
type Error = Error;
fn try_from(value: serde_yaml::Mapping) -> Result<Self, Self::Error> {
let mut nodes = vec![];
for (name, children) in value {
let Value::String(name) = name else {
return Err(Error::UnsupportedType(name.clone()));
};
let mut node = Node::new(name.clone(), vec![], true);
let mut child_nodes: Nodes = children.try_into()?;
node.add_children(&mut child_nodes.0);
nodes.push(node.clone());
}
Ok(nodes.into())
}
}
impl TryFrom<Value> for Nodes {
type Error = Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::Null => Ok(vec![].into()),
Value::String(s) => Ok(s.into()),
Value::Sequence(seq) => seq.try_into(),
Value::Mapping(map) => map.try_into(),
v => Err(Error::UnsupportedType(v.clone())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display() {
let node = Node::new("test".into(), vec![], true);
let filetree = Filetree::new(node);
println!("{}", filetree.root().treeline(&filetree));
assert_eq!(filetree.root().treeline(&filetree), "test")
}
#[cfg(feature = "color")]
#[test]
fn color_display() {
let node = Node::new("test".into(), vec![], true);
let filetree = Filetree::new(node);
println!("{}", filetree.root().treeline_color(&filetree));
assert_eq!(
filetree.root().treeline_color(&filetree),
"test".cyan().bold().to_string()
)
}
}