From 6e10f260521b0b5bae9178d7cebf3523bf1b4a68 Mon Sep 17 00:00:00 2001 From: Benji Nguyen Date: Thu, 28 Apr 2022 00:37:07 -0400 Subject: [PATCH] proto --- .gitignore | 2 + Cargo.lock | 7 +++ Cargo.toml | 8 +++ assets/a.txt | 1 + assets/b.txt | 1 + assets/c/a.txt | 0 src/file_tree/error.rs | 5 ++ src/file_tree/mod.rs | 111 ++++++++++++++++++++++++++++++++++ src/file_tree/tree_node.rs | 119 +++++++++++++++++++++++++++++++++++++ src/main.rs | 8 +++ 10 files changed, 262 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 assets/a.txt create mode 100644 assets/b.txt create mode 100644 assets/c/a.txt create mode 100644 src/file_tree/error.rs create mode 100644 src/file_tree/mod.rs create mode 100644 src/file_tree/tree_node.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..05923927 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..5c9494b7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "erdtree" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..bf01d6bd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "erdtree" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/assets/a.txt b/assets/a.txt new file mode 100644 index 00000000..78981922 --- /dev/null +++ b/assets/a.txt @@ -0,0 +1 @@ +a diff --git a/assets/b.txt b/assets/b.txt new file mode 100644 index 00000000..81bf3969 --- /dev/null +++ b/assets/b.txt @@ -0,0 +1 @@ +ab diff --git a/assets/c/a.txt b/assets/c/a.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/file_tree/error.rs b/src/file_tree/error.rs new file mode 100644 index 00000000..f65e3371 --- /dev/null +++ b/src/file_tree/error.rs @@ -0,0 +1,5 @@ +use std::io; + +pub fn not_dir_err() -> io::Error { + io::Error::new(io::ErrorKind::InvalidInput, "Argument 'root_location' must be a path to a directory.") +} diff --git a/src/file_tree/mod.rs b/src/file_tree/mod.rs new file mode 100644 index 00000000..ef2e7f1f --- /dev/null +++ b/src/file_tree/mod.rs @@ -0,0 +1,111 @@ +#![allow(dead_code)] + +mod error; +mod tree_node; + +use std::path::Path; +use std::fs; +use std::io; +use tree_node::{TreeNode, FileType}; + +pub type FileTreeResult<'a> = Result, io::Error>; + +pub struct FileTree<'a> { + root_location: &'a Path, + root_node: TreeNode +} + +impl<'a> FileTree<'a> { + pub fn new(root_location: &'a S) -> FileTreeResult<'a> + where S: AsRef + ?Sized + { + let root_md = fs::metadata(root_location)?; + + if !root_md.is_dir() { + return Err(error::not_dir_err()) + } + + let root_node = TreeNode::new( + root_location, + FileType::Dir, + ".".to_string(), + 0 + ); + + Ok(Self { + root_location: root_location.as_ref(), + root_node + }) + } + + pub fn get_root_node(&self) -> &TreeNode { + &self.root_node + } + + pub fn len(&self) -> u64 { + self.root_node.get_metadata().len() + } + + pub fn display(&self) { + let root_node = self.get_root_node(); + let buffer = "".to_string(); + let result = Self::sprintf_output(&root_node, buffer); + + println!("{}", result); + } + + fn sprintf_output(node: &TreeNode, mut buffer: String) -> String { + buffer.push_str(&Self::sprintf_row(&node)); + buffer.push_str("\n"); + + for child in node.iter_children() { + if child.is_dir() { + buffer = Self::sprintf_output(child, buffer); + } else { + buffer.push_str(&Self::sprintf_row(&child)); + buffer.push_str("\n"); + } + } + + buffer + } + + fn sprintf_row(node: &TreeNode) -> String { + let mut prefix = "".to_string(); + + for _ in 0..node.get_generation() { + prefix.push_str("\t") + } + + format!("{}{} {}", prefix, node.get_file_name(), node.len()) + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_file_tree() { + use super::FileTree; + use super::tree_node::FileType; + + let file_tree = FileTree::new("./assets/").unwrap(); + let root_node = file_tree.get_root_node(); + + assert_eq!(root_node.get_generation(), 0); + assert_eq!(root_node.num_children(), 3); + + let first_gen_child = root_node.iter_children().nth(0).unwrap(); + + assert_eq!(first_gen_child.get_generation(), 1); + + let second_gen_child = root_node + .iter_children() + .find(|child| child.get_file_type() == &FileType::Dir) + .unwrap() + .iter_children() + .nth(0) + .unwrap(); + + assert_eq!(second_gen_child.get_generation(), 2); + } +} diff --git a/src/file_tree/tree_node.rs b/src/file_tree/tree_node.rs new file mode 100644 index 00000000..51d6572d --- /dev/null +++ b/src/file_tree/tree_node.rs @@ -0,0 +1,119 @@ +use std::fs; +use std::io; +use std::path::{PathBuf, Path}; +use std::slice::Iter; + +pub type TreeNodeResult = Result; +pub type Children = Vec; + +pub struct TreeNode { + location: PathBuf, + file_type: FileType, + file_name: String, + metadata: fs::Metadata, + generation: u64, + children: Children +} + +#[derive(PartialEq)] +pub enum FileType { + File, + Dir, + Symlink, +} + +impl TreeNode { + pub fn new(location: &S, file_type: FileType, file_name: String, generation: u64) -> Self + where S: AsRef + ?Sized + { + let metadata = fs::metadata(location).unwrap(); + + let mut node = Self { + location: location.as_ref().to_path_buf(), + file_type, + file_name, + metadata, + generation, + children: vec![] + }; + + node.construct_branches(generation).unwrap(); + + node + } + + pub fn get_location(&self) -> &Path { + &self.location + } + + pub fn get_metadata(&self) -> &fs::Metadata { + &self.metadata + } + + pub fn is_dir(&self) -> bool { + self.file_type == FileType::Dir + } + + pub fn is_not_dir(&self) -> bool { + !self.is_dir() + } + + pub fn get_file_type(&self) -> &FileType { + &self.file_type + } + + pub fn get_file_name(&self) -> &str { + &self.file_name + } + + pub fn get_generation(&self) -> u64 { + self.generation + } + + pub fn add_child(&mut self, child: Self) { + self.children.push(child); + } + + pub fn iter_children(&self) -> Iter<'_, TreeNode> { + self.children.iter() + } + + pub fn num_children(&self) -> u64 { + self.children.len() as u64 + } + + pub fn len(&self) -> u64 { + self.metadata.len() + } + + fn ascertain_file_type(entry: &fs::DirEntry) -> io::Result { + let file_type = entry.file_type()?; + + if file_type.is_dir() { return Ok(FileType::Dir) } + if file_type.is_file() { return Ok(FileType::File) } + + Ok(FileType::Symlink) + } + + fn construct_branches(&mut self, generation: u64) -> Result<(), io::Error> { + if self.is_not_dir() { return Ok(()) } + + for possible_entry in fs::read_dir(self.get_location())? { + if let Err(_) = possible_entry { continue } + + let entry = possible_entry.unwrap(); + let epath = entry.path(); + let fname = entry.file_name().into_string().unwrap(); + let ftype = match Self::ascertain_file_type(&entry) { + Ok(file_type) => file_type, + _ => continue + }; + + let new_node = Self::new(&epath, ftype, fname, generation + 1); + + self.add_child(new_node); + } + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..4a730fab --- /dev/null +++ b/src/main.rs @@ -0,0 +1,8 @@ +mod file_tree; + +use file_tree::FileTree; + +fn main() { + let file_tree = FileTree::new("./assets/").unwrap(); + file_tree.display(); +}