1 //! Some implementation helpers related to the 'list' command.
3 use std::path::{Path, PathBuf};
5 use indicatif::{ProgressBar, ProgressStyle};
8 use crate::accessible::is_running_in_accessible_mode;
10 /// Options controlling how archive contents should be listed
11 #[derive(Debug, Clone, Copy)]
12 pub struct ListOptions {
13 /// Whether to show a tree view
17 /// Represents a single file in an archive, used in `list::list_files()`
18 #[derive(Debug, Clone)]
19 pub struct FileInArchive {
23 /// Whether this file is a directory
27 /// Actually print the files
28 /// Returns an Error, if one of the files can't be read
31 files: impl IntoIterator<Item = crate::Result<FileInArchive>>,
32 list_options: ListOptions,
33 ) -> crate::Result<()> {
34 println!("Archive: {}", archive.display());
36 if list_options.tree {
37 let pb = if !is_running_in_accessible_mode() {
38 let template = "{wide_msg} [{elapsed_precise}] {spinner:.green}";
39 let pb = ProgressBar::new_spinner();
40 pb.set_style(ProgressStyle::default_bar().template(template));
46 let tree: Tree = files
50 if !is_running_in_accessible_mode() {
53 .set_message(format!("Processing: {}", file.path.display()));
57 .collect::<crate::Result<Tree>>()?;
62 let FileInArchive { path, is_dir } = file?;
63 print_entry(path.display(), is_dir);
69 /// Print an entry and highlight directories, either by coloring them
70 /// if that's supported or by adding a trailing /
71 fn print_entry(name: impl std::fmt::Display, is_dir: bool) {
72 use crate::utils::colors::*;
75 // if colors are deactivated, print final / to mark directories
77 println!("{}/", name);
78 // if in ACCESSIBLE mode, use colors but print final / in case colors
79 // aren't read out aloud with a screen reader or aren't printed on a
81 } else if is_running_in_accessible_mode() {
82 println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
84 println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
87 // not a dir -> just print the file name
92 /// Since archives store files as a list of entries -> without direct
93 /// directory structure (the directories are however part of the name),
94 /// we have to construct the tree structure ourselves to be able to
95 /// display them as a tree
97 use std::{ffi::OsString, iter::FromIterator, path};
99 use linked_hash_map::LinkedHashMap;
101 use super::FileInArchive;
104 #[derive(Debug, Default)]
106 file: Option<FileInArchive>,
107 children: LinkedHashMap<OsString, Tree>,
111 /// Insert a file into the tree
112 pub fn insert(&mut self, file: FileInArchive) {
113 self.insert_(file.clone(), file.path.iter());
115 /// Insert file by traversing the tree recursively
116 fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
117 // Are there more components in the path? -> traverse tree further
118 if let Some(part) = path.next() {
119 // Either insert into an existing child node or create a new one
120 if let Some(t) = self.children.get_mut(part) {
121 t.insert_(file, path)
123 let mut child = Tree::default();
124 child.insert_(file, path);
125 self.children.insert(part.to_os_string(), child);
128 // `path` was empty -> we reached our destination and can insert
129 // `file`, assuming there is no file already there (which meant
130 // there were 2 files with the same name in the same directory
131 // which should be impossible in any sane file system)
133 None => self.file = Some(file),
136 "[warning] multiple files with the same name in a single directory ({})",
144 /// Print the file tree using Unicode line characters
145 pub fn print(&self) {
146 for (i, (name, subtree)) in self.children.iter().enumerate() {
147 subtree.print_(name, String::new(), i == self.children.len() - 1);
150 /// Print the tree by traversing it recursively
151 fn print_(&self, name: &OsString, mut prefix: String, last: bool) {
152 // Convert `name` to valid unicode
153 let name = name.to_string_lossy();
155 // If there are no further elements in the parent directory, add
156 // "└── " to the prefix, otherwise add "├── "
157 let final_part = match last {
158 true => draw::FINAL_LAST,
159 false => draw::FINAL_BRANCH,
162 print!("{}{}", prefix, final_part);
163 let is_dir = match self.file {
164 Some(FileInArchive { is_dir, .. }) => is_dir,
167 super::print_entry(name, is_dir);
169 // Construct prefix for children, adding either a line if this isn't
170 // the last entry in the parent dir or empty space if it is.
171 prefix.push_str(match last {
172 true => draw::PREFIX_EMPTY,
173 false => draw::PREFIX_LINE,
175 // Recursively print all children
176 for (i, (name, subtree)) in self.children.iter().enumerate() {
177 subtree.print_(name, prefix.clone(), i == self.children.len() - 1);
182 impl FromIterator<FileInArchive> for Tree {
183 fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
184 let mut tree = Self::default();
192 /// Constants containing the visual parts of which the displayed tree
195 /// They fall into 2 categories: the `PREFIX_*` parts form the first
196 /// `depth - 1` parts while the `FINAL_*` parts form the last part,
197 /// right before the entry itself
199 /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir
200 /// `PREFIX_LINE`: there are other entries after the corresponding dir
201 /// `FINAL_LAST`: this entry is the last entry in its parent dir
202 /// `FINAL_BRANCH`: there are other entries after this entry
204 /// the corresponding dir is the last entry in its parent dir
205 pub const PREFIX_EMPTY: &str = " ";
206 /// there are other entries after the corresponding dir
207 pub const PREFIX_LINE: &str = "│ ";
208 /// this entry is the last entry in its parent dir
209 pub const FINAL_LAST: &str = "└── ";
210 /// there are other entries after this entry
211 pub const FINAL_BRANCH: &str = "├── ";