mirror of
https://github.com/ouch-org/ouch.git
synced 2025-06-06 11:35:45 +00:00
Implement tree output for 'list' command
This commit is contained in:
parent
30ebcf4f9e
commit
5421a5db77
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -249,6 +249,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.17"
|
||||
@ -303,6 +309,7 @@ dependencies = [
|
||||
"fs-err",
|
||||
"infer",
|
||||
"libc",
|
||||
"linked-hash-map",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"tar",
|
||||
|
@ -25,6 +25,7 @@ xz2 = "0.1.6"
|
||||
zip = { version = "0.5.13", default-features = false, features = ["deflate-miniz"] }
|
||||
flate2 = { version = "1.0.22", default-features = false, features = ["zlib"] }
|
||||
zstd = { version = "0.9.0", default-features = false, features = ["thin"] }
|
||||
linked-hash-map = "0.5.4"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "=3.0.0-beta.5"
|
||||
|
@ -209,7 +209,10 @@ pub fn run(args: Opts, question_policy: QuestionPolicy) -> crate::Result<()> {
|
||||
|
||||
let list_options = ListOptions { tree };
|
||||
|
||||
for (archive_path, formats) in files.iter().zip(formats) {
|
||||
for (i, (archive_path, formats)) in files.iter().zip(formats).enumerate() {
|
||||
if i > 0 {
|
||||
println!();
|
||||
}
|
||||
list_archive_contents(archive_path, formats, list_options)?;
|
||||
}
|
||||
}
|
||||
@ -425,7 +428,7 @@ fn list_archive_contents(
|
||||
if let [Zip] = *formats.as_slice() {
|
||||
let zip_archive = zip::ZipArchive::new(reader)?;
|
||||
let files = crate::archive::zip::list_archive(zip_archive)?;
|
||||
list::list_files(files, list_options);
|
||||
list::list_files(archive_path, files, list_options);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -483,6 +486,6 @@ fn list_archive_contents(
|
||||
panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!");
|
||||
}
|
||||
};
|
||||
list::list_files(files, list_options);
|
||||
list::list_files(archive_path, files, list_options);
|
||||
Ok(())
|
||||
}
|
||||
|
107
src/list.rs
107
src/list.rs
@ -1,6 +1,7 @@
|
||||
//! Implementation of the 'list' command, print list of files in an archive
|
||||
|
||||
use std::path::PathBuf;
|
||||
use self::tree::Tree;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Options controlling how archive contents should be listed
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@ -17,12 +18,112 @@ pub struct FileInArchive {
|
||||
}
|
||||
|
||||
/// Actually print the files
|
||||
pub fn list_files(files: Vec<FileInArchive>, list_options: ListOptions) {
|
||||
pub fn list_files(archive: &Path, files: Vec<FileInArchive>, list_options: ListOptions) {
|
||||
println!("{}:", archive.display());
|
||||
if list_options.tree {
|
||||
todo!("Implement tree view");
|
||||
let tree: Tree = files.into_iter().collect();
|
||||
tree.print();
|
||||
} else {
|
||||
for file in files {
|
||||
println!("{}", file.path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tree {
|
||||
use super::FileInArchive;
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::iter::FromIterator;
|
||||
use std::path;
|
||||
|
||||
const TREE_PREFIX_EMPTY: &str = " ";
|
||||
const TREE_PREFIX_LINE: &str = "│ ";
|
||||
const TREE_FINAL_BRANCH: &str = "├── ";
|
||||
const TREE_FINAL_LAST: &str = "└── ";
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Tree {
|
||||
file: Option<FileInArchive>,
|
||||
children: LinkedHashMap<OsString, Tree>,
|
||||
}
|
||||
impl Tree {
|
||||
/// Insert a file into the tree
|
||||
pub fn insert(&mut self, file: FileInArchive) {
|
||||
self.insert_(file.clone(), file.path.iter());
|
||||
}
|
||||
/// Insert file by traversing the tree recursively
|
||||
fn insert_(&mut self, file: FileInArchive, mut path: path::Iter) {
|
||||
// Are there more components in the path? -> traverse tree further
|
||||
if let Some(part) = path.next() {
|
||||
// Either insert into an existing child node or create a new one
|
||||
if let Some(t) = self.children.get_mut(part) {
|
||||
t.insert_(file, path)
|
||||
} else {
|
||||
let mut child = Tree::default();
|
||||
child.insert_(file, path);
|
||||
self.children.insert(part.to_os_string(), child);
|
||||
}
|
||||
} else {
|
||||
// `path` was empty -> we reached our destination and can insert
|
||||
// `file`, assuming there is no file already there (which meant
|
||||
// there were 2 files with the same name in the same directory
|
||||
// which should be impossible in any sane file system)
|
||||
match &self.file {
|
||||
None => self.file = Some(file),
|
||||
Some(file) => {
|
||||
eprintln!(
|
||||
"[warning] multiple files with the same name in a single directory ({})",
|
||||
file.path.display()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Print the file tree using Unicode line characters
|
||||
pub fn print(&self) {
|
||||
for (i, (name, subtree)) in self.children.iter().enumerate() {
|
||||
subtree.print_(name, String::new(), i == self.children.len() - 1);
|
||||
}
|
||||
}
|
||||
/// Print the tree by traversing it recursively
|
||||
fn print_(&self, name: &OsString, mut prefix: String, last: bool) {
|
||||
// Convert `name` to valid unicode
|
||||
let name = name.to_string_lossy();
|
||||
|
||||
// If there are no further elements in the parent directory, add
|
||||
// "└── " to the prefix, otherwise add "├── "
|
||||
let final_part = match last {
|
||||
true => TREE_FINAL_LAST,
|
||||
false => TREE_FINAL_BRANCH,
|
||||
};
|
||||
|
||||
if let Some(_file) = &self.file {
|
||||
println!("{}{}{}", prefix, final_part, name);
|
||||
} else {
|
||||
println!("{}{}[{}]", prefix, final_part, name);
|
||||
}
|
||||
// Construct prefix for children, adding either a line if this isn't
|
||||
// the last entry in the parent dir or empty space if it is.
|
||||
prefix.push_str(match last {
|
||||
true => TREE_PREFIX_EMPTY,
|
||||
false => TREE_PREFIX_LINE,
|
||||
});
|
||||
// Recursively print all children
|
||||
for (i, (name, subtree)) in self.children.iter().enumerate() {
|
||||
subtree.print_(name, prefix.clone(), i == self.children.len() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<FileInArchive> for Tree {
|
||||
fn from_iter<I: IntoIterator<Item = FileInArchive>>(iter: I) -> Self {
|
||||
let mut tree = Self::default();
|
||||
for file in iter {
|
||||
tree.insert(file);
|
||||
}
|
||||
tree
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user