diff --git a/Cargo.lock b/Cargo.lock index 52c56fcab..de9eb556e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,23 +366,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -407,6 +396,7 @@ dependencies = [ "plist", "proc-mounts", "scoped_threadpool", + "tar", "terminal_size", "timeago", "trycmd", @@ -595,9 +585,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libgit2-sys" @@ -635,9 +625,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "locale" @@ -1008,15 +998,15 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1152,6 +1142,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -1630,6 +1631,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "zoneinfo_compiled" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index cd7ac0f4b..cced66bd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ phf = { version = "0.11.2", features = ["macros"] } plist = { version = "1.6.0", default-features = false } scoped_threadpool = "0.1" uutils_term_grid = "0.3" +tar = "0.4.40" terminal_size = "0.3.0" timeago = { version = "0.4.2", default-features = false } unicode-width = "0.1" diff --git a/src/fs/archive.rs b/src/fs/archive.rs new file mode 100644 index 000000000..f69710c0c --- /dev/null +++ b/src/fs/archive.rs @@ -0,0 +1,144 @@ +use crate::fs::feature::git::GitCache; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use log::*; + +use crate::fs::dir::{DotFilter, Files}; +use crate::fs::File; + +pub trait Directory: Sized { + /// Create a new Dir object filled with all the files in the directory + /// pointed to by the given path. Fails if the directory can’t be read, or + /// isn’t actually a directory, or if there’s an IO error that occurs at + /// any point. + /// + /// The `read_dir` iterator doesn’t actually yield the `.` and `..` + /// entries, so if the user wants to see them, we’ll have to add them + /// ourselves after the files have been read. + fn read_dir(path: PathBuf) -> io::Result; + + /// Produce an iterator of IO results of trying to read all the files in + /// this directory. + fn files<'dir, 'ig>( + &'dir self, + dots: DotFilter, + git: Option<&'ig GitCache>, + git_ignoring: bool, + deref_links: bool, + total_size: bool, + ) -> Files<'dir, 'ig>; + + /// Whether this directory contains a file with the given path. + fn contains(&self, path: &Path) -> bool; + /// Append a path onto the path specified by this directory. + fn join(&self, child: &Path) -> PathBuf; +} + +struct ArchiveEntry { + // name: String, + path: String, + size: u64, + // permissions + // owner + // file type + // mtime +} + +pub enum ArchiveFormat { + Tar, + Unknown, +} + +trait ArchiveReader { + fn read_dir(path: &PathBuf) -> io::Result>; +} + +struct TarReader {} + +impl ArchiveReader for TarReader { + fn read_dir(path: &PathBuf) -> io::Result> { + let result = Vec::new(); + let file_content = fs::File::open(path)?; + tar::Archive::new(file_content).entries().map(|entries| { + for entry in entries { + if let Ok(e) = entry { + let path = e.path().expect("TODO").to_string_lossy().to_string(); + let size = e.size(); + result.push(ArchiveEntry { path, size }); + } + } + })?; + Ok(result) + } +} + +impl ArchiveFormat { + pub fn from_extension(extension: &str) -> Option { + match extension { + ".tar" => Some(ArchiveFormat::Tar), + _ => None, + } + } +} + +pub struct Archive { + //pub file: &'a File<'dir>, + pub format: ArchiveFormat, + + contents: Vec, + pub path: PathBuf, +} + +impl Directory for Archive { + fn read_dir(path: PathBuf) -> io::Result { + let extension = path + .extension() + .unwrap_or(std::ffi::OsStr::new("")) + .to_string_lossy(); + let format = + ArchiveFormat::from_extension(extension.as_ref()).unwrap_or(ArchiveFormat::Unknown); + let contents = match format { + ArchiveFormat::Tar => TarReader::read_dir(&path), + ArchiveFormat::Unknown => { + return Err(io::Error::new( + io::ErrorKind::Unsupported, + "Unsupported archive format", + )) + } + }?; + Ok(Archive { + path, + contents, + format, + }) + } + + /// Produce an iterator of IO results of trying to read all the files in + /// this directory. + fn files<'dir, 'ig>( + &'dir self, + dots: DotFilter, + git: Option<&'ig GitCache>, + git_ignoring: bool, + deref_links: bool, + total_size: bool, + ) -> Files<'dir, 'ig> { + Files { + inner: self.contents.iter(), + dir: self, + dotfiles: dots.shows_dotfiles(), + dots: dots.dots(), + git, + git_ignoring, + deref_links, + total_size, + } + } + + /// Whether this directory contains a file with the given path. + fn contains(&self, path: &Path) -> bool {} + /// Append a path onto the path specified by this directory. + fn join(&self, child: &Path) -> PathBuf {} +} diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 193afebc5..e716b9c37 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -1,3 +1,4 @@ +use crate::fs::archive::{Archive, ArchiveFormat}; use crate::fs::feature::git::GitCache; use crate::fs::fields::GitStatus; use std::fs; @@ -223,7 +224,7 @@ impl Default for DotFilter { impl DotFilter { /// Whether this filter should show dotfiles in a listing. - fn shows_dotfiles(self) -> bool { + pub fn shows_dotfiles(self) -> bool { match self { Self::JustFiles => false, Self::Dotfiles => true, @@ -232,7 +233,7 @@ impl DotFilter { } /// Whether this filter should add dot directories to a listing. - fn dots(self) -> DotsNext { + pub fn dots(self) -> DotsNext { match self { Self::JustFiles => DotsNext::Files, Self::Dotfiles => DotsNext::Files, diff --git a/src/fs/file.rs b/src/fs/file.rs index f410f5fc9..6ccb0ab22 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -20,6 +20,7 @@ use log::*; #[cfg(unix)] use once_cell::sync::Lazy; +use crate::fs::archive::{Archive, ArchiveFormat}; use crate::fs::dir::Dir; use crate::fs::feature::xattr; use crate::fs::feature::xattr::{Attribute, FileAttributes}; @@ -431,6 +432,23 @@ impl<'dir> File<'dir> { } } + /// Interpret file as archive + pub fn archive(&self) -> Option> { + let extension = &self + .path + .extension() + .unwrap_or(std::ffi::OsStr::new("")) + .to_string_lossy(); + if let Some(archive_format) = ArchiveFormat::from_extension(extension.as_ref()) { + Some(Archive { + file: &self, + format: archive_format, + }) + } else { + None + } + } + /// Assuming this file is a symlink, follows that link and any further /// links recursively, returning the result from following the trail. /// diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 0f0d908b2..35ffe5e55 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -4,6 +4,7 @@ pub use self::dir::{Dir, DotFilter}; mod file; pub use self::file::{File, FileTarget}; +pub mod archive; pub mod dir_action; pub mod feature; pub mod fields; diff --git a/src/main.rs b/src/main.rs index ada3ba51d..4f95d3894 100644 --- a/src/main.rs +++ b/src/main.rs @@ -256,6 +256,7 @@ impl<'args> Exa<'args> { debug!("Running with options: {:#?}", self.options); let mut files = Vec::new(); + let mut archives = Vec::new(); let mut dirs = Vec::new(); let mut exit_status = 0; @@ -290,6 +291,14 @@ impl<'args> Exa<'args> { } } + if !self.options.dir_action.treat_dirs_as_files() { + for f in &files { + if let Some(archive) = f.archive() { + archives.push(archive); + } + } + } + // We want to print a directory’s name before we list it, *except* in // the case where it’s the only directory, *except* if there are any // files to print as well. (It’s a double negative) diff --git a/src/options/archive_inspection.rs b/src/options/archive_inspection.rs new file mode 100644 index 000000000..12f5a27a5 --- /dev/null +++ b/src/options/archive_inspection.rs @@ -0,0 +1,18 @@ +use crate::options::parser::MatchedFlags; +use crate::options::{flags, OptionsError}; + +#[derive(Debug, PartialEq)] +pub enum ArchiveInspection { + Always, + Never, +} + +impl ArchiveInspection { + pub fn deduce(matches: &MatchedFlags<'_>) -> Result { + Ok(if matches.has(&flags::INSPECT_ARCHIVES)? { + ArchiveInspection::Always + } else { + ArchiveInspection::Never + }) + } +} diff --git a/src/options/flags.rs b/src/options/flags.rs index 715f5bbfd..29b6fa528 100644 --- a/src/options/flags.rs +++ b/src/options/flags.rs @@ -82,6 +82,7 @@ pub static OCTAL: Arg = Arg { short: Some(b'o'), long: "octal-permis pub static SECURITY_CONTEXT: Arg = Arg { short: Some(b'Z'), long: "context", takes_value: TakesValue::Forbidden }; pub static STDIN: Arg = Arg { short: None, long: "stdin", takes_value: TakesValue::Forbidden }; pub static FILE_FLAGS: Arg = Arg { short: Some(b'O'), long: "flags", takes_value: TakesValue::Forbidden }; +pub static INSPECT_ARCHIVES: Arg = Arg { short: Some(b'q'), long: "quasi", takes_value: TakesValue::Forbidden }; pub static ALL_ARGS: Args = Args(&[ &VERSION, &HELP, diff --git a/src/options/mod.rs b/src/options/mod.rs index c95821522..4f7d752dc 100644 --- a/src/options/mod.rs +++ b/src/options/mod.rs @@ -76,6 +76,7 @@ use crate::options::stdin::FilesInput; use crate::output::{details, grid_details, Mode, View}; use crate::theme::Options as ThemeOptions; +mod archive_inspection; mod dir_action; mod file_name; mod filter;