Merge pull request #217 from Crypto-Spartan/zip-mem-warnings
[ouch.git] / src / list.rs
blob1549b3066e6b0bbd14287e68e0144529ce74404e
1 //! Implementation of the 'list' command, print list of files in an archive
3 use std::path::{Path, PathBuf};
5 use self::tree::Tree;
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
11     pub tree: bool,
14 /// Represents a single file in an archive, used in `list::list_files()`
15 #[derive(Debug, Clone)]
16 pub struct FileInArchive {
17     /// The file path
18     pub path: PathBuf,
20     /// Whether this file is a directory
21     pub is_dir: bool,
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();
29         tree.print();
30     } else {
31         for FileInArchive { path, is_dir } in files {
32             print_entry(path.display(), is_dir);
33         }
34     }
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::*;
42     if is_dir {
43         // if colors are deactivated, print final / to mark directories
44         if BLUE.is_empty() {
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
48         // braille reader
49         } else if *crate::cli::ACCESSIBLE.get().unwrap() {
50             println!("{}{}{}/{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
51         } else {
52             println!("{}{}{}{}", *BLUE, *STYLE_BOLD, name, *ALL_RESET);
53         }
54     } else {
55         // not a dir -> just print the file name
56         println!("{}", name);
57     }
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
64 mod tree {
65     use std::{ffi::OsString, iter::FromIterator, path};
67     use linked_hash_map::LinkedHashMap;
69     use super::FileInArchive;
71     /// Directory tree
72     #[derive(Debug, Default)]
73     pub struct Tree {
74         file: Option<FileInArchive>,
75         children: LinkedHashMap<OsString, Tree>,
76     }
77     impl Tree {
78         /// Insert a file into the tree
79         pub fn insert(&mut self, file: FileInArchive) {
80             self.insert_(file.clone(), file.path.iter());
81         }
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) {
88                     t.insert_(file, path)
89                 } else {
90                     let mut child = Tree::default();
91                     child.insert_(file, path);
92                     self.children.insert(part.to_os_string(), child);
93                 }
94             } else {
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)
99                 match &self.file {
100                     None => self.file = Some(file),
101                     Some(file) => {
102                         eprintln!(
103                             "[warning] multiple files with the same name in a single directory ({})",
104                             file.path.display()
105                         )
106                     }
107                 }
108             }
109         }
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);
115             }
116         }
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,
127             };
129             print!("{}{}", prefix, final_part);
130             let is_dir = match self.file {
131                 Some(FileInArchive { is_dir, .. }) => is_dir,
132                 None => true,
133             };
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,
141             });
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);
145             }
146         }
147     }
149     impl FromIterator<FileInArchive> for Tree {
150         fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
151             let mut tree = Self::default();
152             for file in iter {
153                 tree.insert(file);
154             }
155             tree
156         }
157     }
159     /// Constants containing the visual parts of which the displayed tree
160     /// is constructed.
161     ///
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
165     ///
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
170     mod draw {
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 = "├── ";
179     }