1 //! Implementation of the 'list' command, print list of files in an archive
3 use std::path::{Path, PathBuf};
7 /// Options controlling how archive contents should be listed
8 #[derive(Debug, Clone, Copy)]
9 pub struct ListOptions {
10 /// Whether to show a tree view
14 /// Represents a single file in an archive, used in `list::list_files()`
15 #[derive(Debug, Clone)]
16 pub struct FileInArchive {
20 /// Whether this file is a directory
24 /// Actually print the files
25 pub fn list_files(archive: &Path, files: Vec<FileInArchive>, list_options: ListOptions) {
26 println!("Archive: {}", archive.display());
27 if list_options.tree {
28 let tree: Tree = files.into_iter().collect();
31 for FileInArchive { path, is_dir } in files {
32 print_entry(path.display(), is_dir);
37 /// Print an entry and highlight directories, either by coloring them
38 /// if that's supported or by adding a trailing /
39 fn print_entry(name: impl std::fmt::Display, is_dir: bool) {
40 use crate::utils::colors::*;
43 // if colors are deactivated, print final / to mark directories
45 println!("{}/", name);
46 // if in ACCESSIBLE mode, use colors but print final / in case colors
47 // aren't read out aloud with a screen reader or aren't printed on a
49 } else if *crate::cli::ACCESSIBLE.get().unwrap() {
50 println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
52 println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
55 // not a dir -> just print the file name
60 /// Since archives store files as a list of entries -> without direct
61 /// directory structure (the directories are however part of the name),
62 /// we have to construct the tree structure ourselves to be able to
63 /// display them as a tree
65 use std::{ffi::OsString, iter::FromIterator, path};
67 use linked_hash_map::LinkedHashMap;
69 use super::FileInArchive;
72 #[derive(Debug, Default)]
74 file: Option<FileInArchive>,
75 children: LinkedHashMap<OsString, Tree>,
78 /// Insert a file into the tree
79 pub fn insert(&mut self, file: FileInArchive) {
80 self.insert_(file.clone(), file.path.iter());
82 /// Insert file by traversing the tree recursively
83 fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
84 // Are there more components in the path? -> traverse tree further
85 if let Some(part) = path.next() {
86 // Either insert into an existing child node or create a new one
87 if let Some(t) = self.children.get_mut(part) {
90 let mut child = Tree::default();
91 child.insert_(file, path);
92 self.children.insert(part.to_os_string(), child);
95 // `path` was empty -> we reached our destination and can insert
96 // `file`, assuming there is no file already there (which meant
97 // there were 2 files with the same name in the same directory
98 // which should be impossible in any sane file system)
100 None => self.file = Some(file),
103 "[warning] multiple files with the same name in a single directory ({})",
111 /// Print the file tree using Unicode line characters
112 pub fn print(&self) {
113 for (i, (name, subtree)) in self.children.iter().enumerate() {
114 subtree.print_(name, String::new(), i == self.children.len() - 1);
117 /// Print the tree by traversing it recursively
118 fn print_(&self, name: &OsString, mut prefix: String, last: bool) {
119 // Convert `name` to valid unicode
120 let name = name.to_string_lossy();
122 // If there are no further elements in the parent directory, add
123 // "└── " to the prefix, otherwise add "├── "
124 let final_part = match last {
125 true => draw::FINAL_LAST,
126 false => draw::FINAL_BRANCH,
129 print!("{}{}", prefix, final_part);
130 let is_dir = match self.file {
131 Some(FileInArchive { is_dir, .. }) => is_dir,
134 super::print_entry(name, is_dir);
136 // Construct prefix for children, adding either a line if this isn't
137 // the last entry in the parent dir or empty space if it is.
138 prefix.push_str(match last {
139 true => draw::PREFIX_EMPTY,
140 false => draw::PREFIX_LINE,
142 // Recursively print all children
143 for (i, (name, subtree)) in self.children.iter().enumerate() {
144 subtree.print_(name, prefix.clone(), i == self.children.len() - 1);
149 impl FromIterator<FileInArchive> for Tree {
150 fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
151 let mut tree = Self::default();
159 /// Constants containing the visual parts of which the displayed tree
162 /// They fall into 2 categories: the `PREFIX_*` parts form the first
163 /// `depth - 1` parts while the `FINAL_*` parts form the last part,
164 /// right before the entry itself
166 /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir
167 /// `PREFIX_LINE`: there are other entries after the corresponding dir
168 /// `FINAL_LAST`: this entry is the last entry in its parent dir
169 /// `FINAL_BRANCH`: there are other entries after this entry
171 /// the corresponding dir is the last entry in its parent dir
172 pub const PREFIX_EMPTY: &str = " ";
173 /// there are other entries after the corresponding dir
174 pub const PREFIX_LINE: &str = "│ ";
175 /// this entry is the last entry in its parent dir
176 pub const FINAL_LAST: &str = "└── ";
177 /// there are other entries after this entry
178 pub const FINAL_BRANCH: &str = "├── ";