1 //! Some implementation helpers related to the 'list' command.
3 use std::path::{Path, PathBuf};
6 use crate::accessible::is_running_in_accessible_mode;
8 /// Options controlling how archive contents should be listed
9 #[derive(Debug, Clone, Copy)]
10 pub struct ListOptions {
11 /// Whether to show a tree view
15 /// Represents a single file in an archive, used in `list::list_files()`
16 #[derive(Debug, Clone)]
17 pub struct FileInArchive {
21 /// Whether this file is a directory
25 /// Actually print the files
26 /// Returns an Error, if one of the files can't be read
29 files: impl IntoIterator<Item = crate::Result<FileInArchive>>,
30 list_options: ListOptions,
31 ) -> crate::Result<()> {
32 println!("Archive: {}", archive.display());
34 if list_options.tree {
35 let tree: Tree = files
41 .collect::<crate::Result<Tree>>()?;
45 let FileInArchive { path, is_dir } = file?;
46 print_entry(path.display(), is_dir);
52 /// Print an entry and highlight directories, either by coloring them
53 /// if that's supported or by adding a trailing /
54 fn print_entry(name: impl std::fmt::Display, is_dir: bool) {
55 use crate::utils::colors::*;
58 // if colors are deactivated, print final / to mark directories
60 println!("{}/", name);
61 // if in ACCESSIBLE mode, use colors but print final / in case colors
62 // aren't read out aloud with a screen reader or aren't printed on a
64 } else if is_running_in_accessible_mode() {
65 println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
67 println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
70 // not a dir -> just print the file name
75 /// Since archives store files as a list of entries -> without direct
76 /// directory structure (the directories are however part of the name),
77 /// we have to construct the tree structure ourselves to be able to
78 /// display them as a tree
80 use std::{ffi::OsString, iter::FromIterator, path};
82 use linked_hash_map::LinkedHashMap;
84 use super::FileInArchive;
87 #[derive(Debug, Default)]
89 file: Option<FileInArchive>,
90 children: LinkedHashMap<OsString, Tree>,
94 /// Insert a file into the tree
95 pub fn insert(&mut self, file: FileInArchive) {
96 self.insert_(file.clone(), file.path.iter());
98 /// Insert file by traversing the tree recursively
99 fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
100 // Are there more components in the path? -> traverse tree further
101 if let Some(part) = path.next() {
102 // Either insert into an existing child node or create a new one
103 if let Some(t) = self.children.get_mut(part) {
104 t.insert_(file, path)
106 let mut child = Tree::default();
107 child.insert_(file, path);
108 self.children.insert(part.to_os_string(), child);
111 // `path` was empty -> we reached our destination and can insert
112 // `file`, assuming there is no file already there (which meant
113 // there were 2 files with the same name in the same directory
114 // which should be impossible in any sane file system)
116 None => self.file = Some(file),
119 "[warning] multiple files with the same name in a single directory ({})",
127 /// Print the file tree using Unicode line characters
128 pub fn print(&self) {
129 for (i, (name, subtree)) in self.children.iter().enumerate() {
130 subtree.print_(name, String::new(), i == self.children.len() - 1);
133 /// Print the tree by traversing it recursively
134 fn print_(&self, name: &OsString, mut prefix: String, last: bool) {
135 // Convert `name` to valid unicode
136 let name = name.to_string_lossy();
138 // If there are no further elements in the parent directory, add
139 // "└── " to the prefix, otherwise add "├── "
140 let final_part = match last {
141 true => draw::FINAL_LAST,
142 false => draw::FINAL_BRANCH,
145 print!("{}{}", prefix, final_part);
146 let is_dir = match self.file {
147 Some(FileInArchive { is_dir, .. }) => is_dir,
150 super::print_entry(name, is_dir);
152 // Construct prefix for children, adding either a line if this isn't
153 // the last entry in the parent dir or empty space if it is.
154 prefix.push_str(match last {
155 true => draw::PREFIX_EMPTY,
156 false => draw::PREFIX_LINE,
158 // Recursively print all children
159 for (i, (name, subtree)) in self.children.iter().enumerate() {
160 subtree.print_(name, prefix.clone(), i == self.children.len() - 1);
165 impl FromIterator<FileInArchive> for Tree {
166 fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
167 let mut tree = Self::default();
175 /// Constants containing the visual parts of which the displayed tree
178 /// They fall into 2 categories: the `PREFIX_*` parts form the first
179 /// `depth - 1` parts while the `FINAL_*` parts form the last part,
180 /// right before the entry itself
182 /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir
183 /// `PREFIX_LINE`: there are other entries after the corresponding dir
184 /// `FINAL_LAST`: this entry is the last entry in its parent dir
185 /// `FINAL_BRANCH`: there are other entries after this entry
187 /// the corresponding dir is the last entry in its parent dir
188 pub const PREFIX_EMPTY: &str = " ";
189 /// there are other entries after the corresponding dir
190 pub const PREFIX_LINE: &str = "│ ";
191 /// this entry is the last entry in its parent dir
192 pub const FINAL_LAST: &str = "└── ";
193 /// there are other entries after this entry
194 pub const FINAL_BRANCH: &str = "├── ";