1 //! Some implementation helpers related to the 'list' command.
9 use crate::{accessible::is_running_in_accessible_mode, utils::EscapedPathDisplay};
11 /// Options controlling how archive contents should be listed
12 #[derive(Debug, Clone, Copy)]
13 pub struct ListOptions {
14 /// Whether to show a tree view
18 /// Represents a single file in an archive, used in `list::list_files()`
19 #[derive(Debug, Clone)]
20 pub struct FileInArchive {
24 /// Whether this file is a directory
28 /// Actually print the files
29 /// Returns an Error, if one of the files can't be read
32 files: impl IntoIterator<Item = crate::Result<FileInArchive>>,
33 list_options: ListOptions,
34 ) -> crate::Result<()> {
35 let out = &mut stdout().lock();
36 let _ = writeln!(out, "Archive: {}", EscapedPathDisplay::new(archive));
38 if list_options.tree {
39 let tree = files.into_iter().collect::<crate::Result<Tree>>()?;
43 let FileInArchive { path, is_dir } = file?;
44 print_entry(out, EscapedPathDisplay::new(&path), is_dir);
50 /// Print an entry and highlight directories, either by coloring them
51 /// if that's supported or by adding a trailing /
52 fn print_entry(out: &mut impl Write, name: impl std::fmt::Display, is_dir: bool) {
53 use crate::utils::colors::*;
56 // if colors are deactivated, print final / to mark directories
58 let _ = writeln!(out, "{name}/");
59 // if in ACCESSIBLE mode, use colors but print final / in case colors
60 // aren't read out aloud with a screen reader or aren't printed on a
62 } else if is_running_in_accessible_mode() {
63 let _ = writeln!(out, "{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
65 let _ = writeln!(out, "{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
68 // not a dir -> just print the file name
69 let _ = writeln!(out, "{name}");
73 /// Since archives store files as a list of entries -> without direct
74 /// directory structure (the directories are however part of the name),
75 /// we have to construct the tree structure ourselves to be able to
76 /// display them as a tree
79 ffi::{OsStr, OsString},
85 use bstr::{ByteSlice, ByteVec};
86 use linked_hash_map::LinkedHashMap;
88 use super::FileInArchive;
89 use crate::{utils::EscapedPathDisplay, warning};
92 #[derive(Debug, Default)]
94 file: Option<FileInArchive>,
95 children: LinkedHashMap<OsString, Tree>,
99 /// Insert a file into the tree
100 pub fn insert(&mut self, file: FileInArchive) {
101 self.insert_(file.clone(), file.path.iter());
103 /// Insert file by traversing the tree recursively
104 fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
105 // Are there more components in the path? -> traverse tree further
106 if let Some(part) = path.next() {
107 // Either insert into an existing child node or create a new one
108 if let Some(t) = self.children.get_mut(part) {
109 t.insert_(file, path)
111 let mut child = Tree::default();
112 child.insert_(file, path);
113 self.children.insert(part.to_os_string(), child);
116 // `path` was empty -> we reached our destination and can insert
117 // `file`, assuming there is no file already there (which meant
118 // there were 2 files with the same name in the same directory
119 // which should be impossible in any sane file system)
121 None => self.file = Some(file),
124 "multiple files with the same name in a single directory ({})",
125 EscapedPathDisplay::new(&file.path),
132 /// Print the file tree using Unicode line characters
133 pub fn print(&self, out: &mut impl Write) {
134 for (i, (name, subtree)) in self.children.iter().enumerate() {
135 subtree.print_(out, name, "", i == self.children.len() - 1);
138 /// Print the tree by traversing it recursively
139 fn print_(&self, out: &mut impl Write, name: &OsStr, prefix: &str, last: bool) {
140 // If there are no further elements in the parent directory, add
141 // "└── " to the prefix, otherwise add "├── "
142 let final_part = match last {
143 true => draw::FINAL_LAST,
144 false => draw::FINAL_BRANCH,
147 print!("{prefix}{final_part}");
148 let is_dir = match self.file {
149 Some(FileInArchive { is_dir, .. }) => is_dir,
152 super::print_entry(out, <Vec<u8> as ByteVec>::from_os_str_lossy(name).as_bstr(), is_dir);
154 // Construct prefix for children, adding either a line if this isn't
155 // the last entry in the parent dir or empty space if it is.
156 let mut prefix = prefix.to_owned();
157 prefix.push_str(match last {
158 true => draw::PREFIX_EMPTY,
159 false => draw::PREFIX_LINE,
161 // Recursively print all children
162 for (i, (name, subtree)) in self.children.iter().enumerate() {
163 subtree.print_(out, name, &prefix, i == self.children.len() - 1);
168 impl FromIterator<FileInArchive> for Tree {
169 fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
170 let mut tree = Self::default();
178 /// Constants containing the visual parts of which the displayed tree
181 /// They fall into 2 categories: the `PREFIX_*` parts form the first
182 /// `depth - 1` parts while the `FINAL_*` parts form the last part,
183 /// right before the entry itself
185 /// `PREFIX_EMPTY`: the corresponding dir is the last entry in its parent dir
186 /// `PREFIX_LINE`: there are other entries after the corresponding dir
187 /// `FINAL_LAST`: this entry is the last entry in its parent dir
188 /// `FINAL_BRANCH`: there are other entries after this entry
190 /// the corresponding dir is the last entry in its parent dir
191 pub const PREFIX_EMPTY: &str = " ";
192 /// there are other entries after the corresponding dir
193 pub const PREFIX_LINE: &str = "│ ";
194 /// this entry is the last entry in its parent dir
195 pub const FINAL_LAST: &str = "└── ";
196 /// there are other entries after this entry
197 pub const FINAL_BRANCH: &str = "├── ";