//! Implementation of the 'list' command, print list of files in an archive use std::path::{Path, PathBuf}; use self::tree::Tree; /// Options controlling how archive contents should be listed #[derive(Debug, Clone, Copy)] pub struct ListOptions { /// Whether to show a tree view pub tree: bool, } /// Represents a single file in an archive, used in `list::list_files()` #[derive(Debug, Clone)] pub struct FileInArchive { /// The file path pub path: PathBuf, /// Whether this file is a directory pub is_dir: bool, } /// Actually print the files pub fn list_files(archive: &Path, files: Vec, list_options: ListOptions) { println!("Archiv: {}", archive.display()); if list_options.tree { let tree: Tree = files.into_iter().collect(); tree.print(); } else { for FileInArchive { path, is_dir } in files { print_entry(path.display(), is_dir); } } } /// Print an entry and highlight directories, either by coloring them /// if that's supported or by adding a trailing / fn print_entry(name: impl std::fmt::Display, is_dir: bool) { use crate::utils::colors::*; if is_dir { // if colors are deactivated, print final / to mark directories if BLUE.is_empty() { println!("{}/", name); // if in ACCESSIBLE mode, use colors but print final / in case colors // aren't read out aloud with a screen reader or aren't printed on a // braille reader } else if *crate::cli::ACCESSIBLE.get().unwrap() { println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET); } else { println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET); } } else { // not a dir -> just print the file name println!("{}", name); } } /// Since archives store files as a list of entries -> without direct /// directory structure (the directories are however part of the name), /// we have to construct the tree structure ourselves to be able to /// display them as a tree mod tree { use std::{ffi::OsString, iter::FromIterator, path}; use linked_hash_map::LinkedHashMap; use super::FileInArchive; /// Directory tree #[derive(Debug, Default)] pub struct Tree { file: Option, children: LinkedHashMap, } impl Tree { /// Insert a file into the tree pub fn insert(&mut self, file: FileInArchive) { self.insert_(file.clone(), file.path.iter()); } /// Insert file by traversing the tree recursively fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) { // Are there more components in the path? -> traverse tree further if let Some(part) = path.next() { // Either insert into an existing child node or create a new one if let Some(t) = self.children.get_mut(part) { t.insert_(file, path) } else { let mut child = Tree::default(); child.insert_(file, path); self.children.insert(part.to_os_string(), child); } } else { // `path` was empty -> we reached our destination and can insert // `file`, assuming there is no file already there (which meant // there were 2 files with the same name in the same directory // which should be impossible in any sane file system) match &self.file { None => self.file = Some(file), Some(file) => { eprintln!( "[warning] multiple files with the same name in a single directory ({})", file.path.display() ) } } } } /// Print the file tree using Unicode line characters pub fn print(&self) { for (i, (name, subtree)) in self.children.iter().enumerate() { subtree.print_(name, String::new(), i == self.children.len() - 1); } } /// Print the tree by traversing it recursively fn print_(&self, name: &OsString, mut prefix: String, last: bool) { // Convert `name` to valid unicode let name = name.to_string_lossy(); // If there are no further elements in the parent directory, add // "└── " to the prefix, otherwise add "├── " let final_part = match last { true => draw::FINAL_LAST, false => draw::FINAL_BRANCH, }; print!("{}{}", prefix, final_part); let is_dir = match self.file { Some(FileInArchive { is_dir, .. }) => is_dir, None => true, }; super::print_entry(name, is_dir); // Construct prefix for children, adding either a line if this isn't // the last entry in the parent dir or empty space if it is. prefix.push_str(match last { true => draw::PREFIX_EMPTY, false => draw::PREFIX_LINE, }); // Recursively print all children for (i, (name, subtree)) in self.children.iter().enumerate() { subtree.print_(name, prefix.clone(), i == self.children.len() - 1); } } } impl FromIterator for Tree { fn from_iter>(iter: I) -> Self { let mut tree = Self::default(); for file in iter { tree.insert(file); } tree } } /// Constants containing the visual parts of which the displayed tree /// is constructed. /// /// They fall into 2 categories: the `PREFIX_*` parts form the first /// `depth - 1` parts while the `FINAL_*` parts form the last part, /// right before the entry itself /// /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir /// `PREFIX_LINE`: there are other entries after the corresponding dir /// `FINAL_LAST`: this entry is the last entry in its parent dir /// `FINAL_BRANCH`: there are other entries after this entry mod draw { /// the corresponding dir is the last entry in its parent dir pub const PREFIX_EMPTY: &str = " "; /// there are other entries after the corresponding dir pub const PREFIX_LINE: &str = "│ "; /// this entry is the last entry in its parent dir pub const FINAL_LAST: &str = "└── "; /// there are other entries after this entry pub const FINAL_BRANCH: &str = "├── "; } }