1 //! Implementation of the 'list' command, print list of files in an archive
3 use std::path::{Path, PathBuf};
5 use indicatif::{ProgressBar, ProgressStyle};
9 /// Options controlling how archive contents should be listed
10 #[derive(Debug, Clone, Copy)]
11 pub struct ListOptions {
12 /// Whether to show a tree view
16 /// Represents a single file in an archive, used in `list::list_files()`
17 #[derive(Debug, Clone)]
18 pub struct FileInArchive {
22 /// Whether this file is a directory
26 /// Actually print the files
27 /// Returns an Error, if one of the files can't be read
30 files: impl IntoIterator<Item = crate::Result<FileInArchive>>,
31 list_options: ListOptions,
32 ) -> crate::Result<()> {
33 println!("Archive: {}", archive.display());
35 if list_options.tree {
36 let pb = if !crate::cli::ACCESSIBLE.get().unwrap() {
37 let template = "{wide_msg} [{elapsed_precise}] {spinner:.green}";
38 let pb = ProgressBar::new_spinner();
39 pb.set_style(ProgressStyle::default_bar().template(template));
45 let tree: Tree = files
49 if !crate::cli::ACCESSIBLE.get().unwrap() {
50 pb.as_ref().expect("exists").set_message(format!("Processing: {}", file.path.display()));
54 .collect::<crate::Result<Tree>>()?;
59 let FileInArchive { path, is_dir } = file?;
60 print_entry(path.display(), is_dir);
66 /// Print an entry and highlight directories, either by coloring them
67 /// if that's supported or by adding a trailing /
68 fn print_entry(name: impl std::fmt::Display, is_dir: bool) {
69 use crate::utils::colors::*;
72 // if colors are deactivated, print final / to mark directories
74 println!("{}/", name);
75 // if in ACCESSIBLE mode, use colors but print final / in case colors
76 // aren't read out aloud with a screen reader or aren't printed on a
78 } else if *crate::cli::ACCESSIBLE.get().unwrap() {
79 println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
81 println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
84 // not a dir -> just print the file name
89 /// Since archives store files as a list of entries -> without direct
90 /// directory structure (the directories are however part of the name),
91 /// we have to construct the tree structure ourselves to be able to
92 /// display them as a tree
94 use std::{ffi::OsString, iter::FromIterator, path};
96 use linked_hash_map::LinkedHashMap;
98 use super::FileInArchive;
101 #[derive(Debug, Default)]
103 file: Option<FileInArchive>,
104 children: LinkedHashMap<OsString, Tree>,
107 /// Insert a file into the tree
108 pub fn insert(&mut self, file: FileInArchive) {
109 self.insert_(file.clone(), file.path.iter());
111 /// Insert file by traversing the tree recursively
112 fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
113 // Are there more components in the path? -> traverse tree further
114 if let Some(part) = path.next() {
115 // Either insert into an existing child node or create a new one
116 if let Some(t) = self.children.get_mut(part) {
117 t.insert_(file, path)
119 let mut child = Tree::default();
120 child.insert_(file, path);
121 self.children.insert(part.to_os_string(), child);
124 // `path` was empty -> we reached our destination and can insert
125 // `file`, assuming there is no file already there (which meant
126 // there were 2 files with the same name in the same directory
127 // which should be impossible in any sane file system)
129 None => self.file = Some(file),
132 "[warning] multiple files with the same name in a single directory ({})",
140 /// Print the file tree using Unicode line characters
141 pub fn print(&self) {
142 for (i, (name, subtree)) in self.children.iter().enumerate() {
143 subtree.print_(name, String::new(), i == self.children.len() - 1);
146 /// Print the tree by traversing it recursively
147 fn print_(&self, name: &OsString, mut prefix: String, last: bool) {
148 // Convert `name` to valid unicode
149 let name = name.to_string_lossy();
151 // If there are no further elements in the parent directory, add
152 // "└── " to the prefix, otherwise add "├── "
153 let final_part = match last {
154 true => draw::FINAL_LAST,
155 false => draw::FINAL_BRANCH,
158 print!("{}{}", prefix, final_part);
159 let is_dir = match self.file {
160 Some(FileInArchive { is_dir, .. }) => is_dir,
163 super::print_entry(name, is_dir);
165 // Construct prefix for children, adding either a line if this isn't
166 // the last entry in the parent dir or empty space if it is.
167 prefix.push_str(match last {
168 true => draw::PREFIX_EMPTY,
169 false => draw::PREFIX_LINE,
171 // Recursively print all children
172 for (i, (name, subtree)) in self.children.iter().enumerate() {
173 subtree.print_(name, prefix.clone(), i == self.children.len() - 1);
178 impl FromIterator<FileInArchive> for Tree {
179 fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
180 let mut tree = Self::default();
188 /// Constants containing the visual parts of which the displayed tree
191 /// They fall into 2 categories: the `PREFIX_*` parts form the first
192 /// `depth - 1` parts while the `FINAL_*` parts form the last part,
193 /// right before the entry itself
195 /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir
196 /// `PREFIX_LINE`: there are other entries after the corresponding dir
197 /// `FINAL_LAST`: this entry is the last entry in its parent dir
198 /// `FINAL_BRANCH`: there are other entries after this entry
200 /// the corresponding dir is the last entry in its parent dir
201 pub const PREFIX_EMPTY: &str = " ";
202 /// there are other entries after the corresponding dir
203 pub const PREFIX_LINE: &str = "│ ";
204 /// this entry is the last entry in its parent dir
205 pub const FINAL_LAST: &str = "└── ";
206 /// there are other entries after this entry
207 pub const FINAL_BRANCH: &str = "├── ";