diff options
author | Pascal H <hpwxf@haveneer.com> | 2023-04-30 18:26:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-01 00:26:15 +0800 |
commit | 2fe3fcdd3564836962eab8ba6b1444996fe24e1e (patch) | |
tree | fad504cec71bd00aa6b1a5e6c34fe042e4a1cfec | |
parent | 6840c01905a7601b0d7f0d4dc2e08c5a5a581321 (diff) |
Git integration (#822)
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.lock | 62 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | build.rs | 11 | ||||
-rw-r--r-- | ci/before_deploy.bash | 2 | ||||
-rw-r--r-- | doc/lsd.md | 7 | ||||
-rw-r--r-- | src/app.rs | 49 | ||||
-rw-r--r-- | src/color.rs | 7 | ||||
-rw-r--r-- | src/config_file.rs | 4 | ||||
-rw-r--r-- | src/core.rs | 32 | ||||
-rw-r--r-- | src/display.rs | 180 | ||||
-rw-r--r-- | src/flags.rs | 1 | ||||
-rw-r--r-- | src/flags/blocks.rs | 59 | ||||
-rw-r--r-- | src/flags/sorting.rs | 29 | ||||
-rw-r--r-- | src/git.rs | 460 | ||||
-rw-r--r-- | src/git_theme.rs | 31 | ||||
-rw-r--r-- | src/icon.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/meta/git_file_status.rs | 67 | ||||
-rw-r--r-- | src/meta/mod.rs | 14 | ||||
-rw-r--r-- | src/meta/name.rs | 2 | ||||
-rw-r--r-- | src/sort.rs | 5 | ||||
-rw-r--r-- | src/theme.rs | 3 | ||||
-rw-r--r-- | src/theme/color.rs | 19 | ||||
-rw-r--r-- | src/theme/git.rs | 35 |
26 files changed, 1056 insertions, 38 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2e0b4..de3a46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Add [Git integration](https://github.com/Peltoche/lsd/issues/7) from [hpwxf](https://github.com/hpwxf) - In keeping with the coreutils change, add quotes and escapes for necessary filenames from [merelymyself](https://github.com/merelymyself) - Add support for icon theme from [zwpaper](https://github.com/zwpaper) - Add icon for kt and kts from [LeeWeeder](https://github.com/LeeWeeder) @@ -82,6 +82,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -296,6 +299,19 @@ dependencies = [ ] [[package]] +name = "git2" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -432,6 +448,15 @@ dependencies = [ ] [[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] name = "js-sys" version = "0.3.58" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -453,6 +478,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] +name = "libgit2-sys" +version = "0.14.2+1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "linked-hash-map" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -503,6 +552,7 @@ dependencies = [ "clap_complete", "crossterm", "dirs", + "git2", "globset", "human-sort", "libc", @@ -644,6 +694,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] name = "predicates" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1105,6 +1161,12 @@ dependencies = [ ] [[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -21,7 +21,7 @@ clap_complete = "4.1" version_check = "0.9.*" [dependencies] -crossterm = { version = "0.24.0", features = ["serde"]} +crossterm = { version = "0.24.0", features = ["serde"] } dirs = "3.0.*" libc = "0.2.*" human-sort = "0.2.2" @@ -42,6 +42,10 @@ serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" url = "2.1.*" +[target."cfg(not(all(windows, target_arch = \"x86\", target_env = \"gnu\")))".dependencies] +# if ssl feature is enabled compilation will fail on arm-unknown-linux-gnueabihf and i686-pc-windows-gnu +git2 = { version = "0.16", optional = true, default-features = false } + [target.'cfg(unix)'.dependencies] users = "0.11.*" xattr = "0.2.*" @@ -61,7 +65,9 @@ tempfile = "3" serial_test = "0.5" [features] +default = ["git2"] sudo = [] +no-git = [] # force disabling git even if available by default [profile.release] lto = true @@ -103,7 +103,7 @@ classic: false # == Blocks == # This specifies the columns and their order when using the long and the tree # layout. -# Possible values: permission, user, group, context, size, date, name, inode, links +# Possible values: permission, user, group, context, size, date, name, inode, links, git blocks: - permission - user @@ -34,4 +34,15 @@ fn main() { generate_to(Zsh, &mut app, bin_name, &outdir).expect("Failed to generate Zsh completions"); generate_to(PowerShell, &mut app, bin_name, &outdir) .expect("Failed to generate PowerShell completions"); + + // Disable git feature for these target where git2 is not well supported + if !std::env::var("CARGO_FEATURE_GIT2") + .map(|flag| flag == "1") + .unwrap_or(false) + || std::env::var("TARGET") + .map(|target| target == "i686-pc-windows-gnu") + .unwrap_or(false) + { + println!(r#"cargo:rustc-cfg=feature="no-git""#); + } } diff --git a/ci/before_deploy.bash b/ci/before_deploy.bash index e4afd5c..9c8f045 100644 --- a/ci/before_deploy.bash +++ b/ci/before_deploy.bash @@ -4,7 +4,7 @@ set -ex build() { - cargo build --target "$TARGET" --release --verbose + cargo build --target "$TARGET" --features="$FEATURES" --release --verbose } pack() { @@ -38,6 +38,9 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich `-X`, `--extensionsort` : Sort by file extension +`--git` +: Display git status. Directory git status is a reduction of included file statuses (recursively). + `--help` : Prints help information @@ -90,7 +93,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich : Natural sort of (version) numbers within text `--blocks <blocks>...` -: Specify the blocks that will be displayed and in what order [possible values: permission, user, group, size, date, name, inode] +: Specify the blocks that will be displayed and in what order [possible values: permission, user, group, size, date, name, inode, git] `--color <color>...` : When to use terminal colours [default: auto] [possible values: always, auto, never] @@ -126,7 +129,7 @@ lsd is a ls command with a lot of pretty colours and some other stuff to enrich : How to display size [default: default] [possible values: default, short, bytes] `--sort <WORD>...` -: Sort by WORD instead of name [possible values: size, time, version, extension] +: Sort by WORD instead of name [possible values: size, time, version, extension, git] `-U`, `--no-sort` : Do not sort. List entries in directory order @@ -96,6 +96,10 @@ pub struct Cli { #[arg(short = 'X', long)] pub extensionsort: bool, + /// Sort by git status + #[arg(short = 'G', long)] + pub gitsort: bool, + /// Natural sort of (version) numbers within text #[arg(short = 'v', long)] pub versionsort: bool, @@ -104,13 +108,13 @@ pub struct Cli { #[arg( long, value_name = "TYPE", - value_parser = ["size", "time", "version", "extension", "none"], - overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "no_sort"] + value_parser = ["size", "time", "version", "extension", "git", "none"], + overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "no_sort"] )] pub sort: Option<String>, /// Do not sort. List entries in directory order - #[arg(short = 'U', long, overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "sort"])] + #[arg(short = 'U', long, overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "sort"])] pub no_sort: bool, /// Reverse the order of the sort @@ -127,9 +131,9 @@ pub struct Cli { /// Specify the blocks that will be displayed and in what order #[arg( - long, - value_delimiter = ',', - value_parser = ["permission", "user", "group", "context", "size", "date", "name", "inode", "links"], + long, + value_delimiter = ',', + value_parser = ["permission", "user", "group", "context", "size", "date", "name", "inode", "links", "git"], )] pub blocks: Vec<String>, @@ -150,6 +154,11 @@ pub struct Cli { #[arg(short, long)] pub inode: bool, + /// Show git status on file and directory" + /// Only when used with --long option + #[arg(short, long)] + pub git: bool, + /// When showing file information for a symbolic link, /// show information for the file the link references rather than for the link itself #[arg(short = 'L', long)] @@ -196,15 +205,15 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> { Some('f') => (), Some(n @ ('3' | '6' | '9')) => match chars.next() { Some('f') => (), - Some(c) => return Err(format!("invalid format specifier: %.{}{}", n, c)), + Some(c) => return Err(format!("invalid format specifier: %.{n}{c}")), None => return Err("missing format specifier".to_owned()), }, - Some(c) => return Err(format!("invalid format specifier: %.{}", c)), + Some(c) => return Err(format!("invalid format specifier: %.{c}")), None => return Err("missing format specifier".to_owned()), }, Some(n @ (':' | '#')) => match chars.next() { Some('z') => (), - Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)), + Some(c) => return Err(format!("invalid format specifier: %{n}{c}")), None => return Err("missing format specifier".to_owned()), }, Some(n @ ('-' | '_' | '0')) => match chars.next() { @@ -212,7 +221,7 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> { 'C' | 'd' | 'e' | 'f' | 'G' | 'g' | 'H' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm' | 'S' | 's' | 'U' | 'u' | 'V' | 'W' | 'w' | 'Y' | 'y', ) => (), - Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)), + Some(c) => return Err(format!("invalid format specifier: %{n}{c}")), None => return Err("missing format specifier".to_owned()), }, Some( @@ -223,10 +232,10 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> { ) => (), Some(n @ ('3' | '6' | '9')) => match chars.next() { Some('f') => (), - Some(c) => return Err(format!("invalid format specifier: %{}{}", n, c)), + Some(c) => return Err(format!("invalid format specifier: %{n}{c}")), None => return Err("missing format specifier".to_owned()), }, - Some(c) => return Err(format!("invalid format specifier: %{}", c)), + Some(c) => return Err(format!("invalid format specifier: %{c}")), None => return Err("missing format specifier".to_owned()), }, None => break, @@ -235,3 +244,19 @@ pub fn validate_time_format(formatter: &str) -> Result<String, String> { } Ok(formatter.to_owned()) } + +// Wrapper for value_parser to simply remove non supported option (mainly git flag) +// required since value_parser requires impl Into<ValueParser> that Vec do not support +// should be located here, since this file is included by build.rs +struct LabelFilter<Filter: Fn(&'static str) -> bool, const C: usize>([&'static str; C], Filter); + +impl<Filter: Fn(&'static str) -> bool, const C: usize> From<LabelFilter<Filter, C>> + for clap::builder::ValueParser +{ + fn from(label_filter: LabelFilter<Filter, C>) -> Self { + let filter = label_filter.1; + let values = label_filter.0.into_iter().filter(|x| filter(x)); + let inner = clap::builder::PossibleValuesParser::from(values); + Self::from(inner) + } +} diff --git a/src/color.rs b/src/color.rs index c054d5a..ed69849 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,6 +4,7 @@ use lscolors::{Indicator, LsColors}; use std::path::Path; pub use crate::flags::color::ThemeOption; +use crate::git::GitStatus; use crate::theme::{color::ColorTheme, Theme}; #[allow(dead_code)] @@ -61,6 +62,10 @@ pub enum Elem { }, TreeEdge, + + GitStatus { + status: GitStatus, + }, } impl Elem { @@ -121,6 +126,7 @@ impl Elem { Elem::TreeEdge => theme.tree_edge, Elem::Links { valid: false } => theme.links.invalid, Elem::Links { valid: true } => theme.links.valid, + Elem::GitStatus { .. } => theme.git_status.default, } } } @@ -389,6 +395,7 @@ mod elem { invalid: Color::AnsiValue(245), // Grey }, tree_edge: Color::AnsiValue(245), // Grey + git_status: Default::default(), } } diff --git a/src/config_file.rs b/src/config_file.rs index d2c6d7c..c9392c8 100644 --- a/src/config_file.rs +++ b/src/config_file.rs @@ -203,7 +203,7 @@ classic: false # == Blocks == # This specifies the columns and their order when using the long and the tree # layout. -# Possible values: permission, user, group, context, size, date, name, inode +# Possible values: permission, user, group, context, size, date, name, inode, git blocks: - permission - user @@ -388,7 +388,7 @@ mod tests { total_size: Some(false), symlink_arrow: Some("⇒".into()), hyperlink: Some(HyperlinkOption::Never), - header: None + header: None, }, c ); diff --git a/src/core.rs b/src/core.rs index 9377c07..b0d0efc 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,7 +1,9 @@ use crate::color::Colors; use crate::display; use crate::flags::{ColorOption, Display, Flags, HyperlinkOption, Layout, SortOrder, ThemeOption}; +use crate::git::GitCache; use crate::icon::Icons; + use crate::meta::Meta; use crate::{print_error, print_output, sort, ExitCode}; use std::path::PathBuf; @@ -11,6 +13,8 @@ use std::io; #[cfg(not(target_os = "windows"))] use std::os::unix::io::AsRawFd; +use crate::flags::blocks::Block; +use crate::git_theme::GitTheme; #[cfg(target_os = "windows")] use terminal_size::terminal_size; @@ -18,6 +22,7 @@ pub struct Core { flags: Flags, icons: Icons, colors: Colors, + git_theme: GitTheme, sorters: Vec<(SortOrder, sort::SortFn)>, } @@ -75,6 +80,7 @@ impl Core { flags, colors: Colors::new(color_theme), icons: Icons::new(tty_available, icon_when, icon_theme, icon_separator), + git_theme: GitTheme::new(), sorters, } } @@ -106,12 +112,19 @@ impl Core { } }; + let cache = if self.flags.blocks.0.contains(&Block::GitStatus) { + Some(GitCache::new(&path)) + } else { + None + }; + let recurse = self.flags.layout == Layout::Tree || self.flags.display != Display::DirectoryOnly; if recurse { - match meta.recurse_into(depth, &self.flags) { + match meta.recurse_into(depth, &self.flags, cache.as_ref()) { Ok((content, path_exit_code)) => { meta.content = content; + meta.git_status = cache.and_then(|cache| cache.get(&meta.path, true)); meta_list.push(meta); exit_code.set_if_greater(path_exit_code); } @@ -122,6 +135,7 @@ impl Core { } }; } else { + meta.git_status = cache.and_then(|cache| cache.get(&meta.path, true)); meta_list.push(meta); }; } @@ -147,9 +161,21 @@ impl Core { fn display(&self, metas: &[Meta]) { let output = if self.flags.layout == Layout::Tree { - display::tree(metas, &self.flags, &self.colors, &self.icons) + display::tree( + metas, + &self.flags, + &self.colors, + &self.icons, + &self.git_theme, + ) } else { - display::grid(metas, &self.flags, &self.colors, &self.icons) + display::grid( + metas, + &self.flags, + &self.colors, + &self.icons, + &self.git_theme, + ) }; print_output!("{}", output); diff --git a/src/display.rs b/src/display.rs index 8e7c251..6fea6e9 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,5 +1,7 @@ use crate::color::{Colors, Elem}; -use crate::flags::{Block, Display, Flags, HyperlinkOption, Layout}; +use crate::flags::blocks::Block; +use crate::flags::{Display, Flags, HyperlinkOption, Layout}; +use crate::git_theme::GitTheme; use crate::icon::Icons; use crate::meta::name::DisplayOption; use crate::meta::{FileType, Meta}; @@ -13,7 +15,13 @@ const LINE: &str = "\u{2502} "; // "│ " const CORNER: &str = "\u{2514}\u{2500}\u{2500}"; // "└──" const BLANK: &str = " "; -pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String { +pub fn grid( + metas: &[Meta], + flags: &Flags, + colors: &Colors, + icons: &Icons, + git_theme: &GitTheme, +) -> String { let term_width = terminal_size().map(|(w, _)| w.0 as usize); inner_display_grid( @@ -22,12 +30,19 @@ pub fn grid(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St flags, colors, icons, + git_theme, 0, term_width, ) } -pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> String { +pub fn tree( + metas: &[Meta], + flags: &Flags, + colors: &Colors, + icons: &Icons, + git_theme: &GitTheme, +) -> String { let mut grid = Grid::new(GridOptions { filling: Filling::Spaces(1), direction: Direction::LeftToRight, @@ -42,19 +57,30 @@ pub fn tree(metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons) -> St } } - for cell in inner_display_tree(metas, flags, colors, icons, (0, ""), &padding_rules, index) { + for cell in inner_display_tree( + metas, + flags, + colors, + icons, + git_theme, + (0, ""), + &padding_rules, + index, + ) { grid.add(cell); } grid.fit_into_columns(flags.blocks.0.len()).to_string() } +#[allow(clippy::too_many_arguments)] // should wrap flags, colors, icons, git_theme into one struct fn inner_display_grid( display_option: &DisplayOption, metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons, + git_theme: &GitTheme, depth: usize, term_width: Option<usize>, ) -> String { @@ -93,6 +119,7 @@ fn inner_display_grid( meta, colors, icons, + git_theme, flags, display_option, &padding_rules, @@ -152,6 +179,7 @@ fn inner_display_grid( flags, colors, icons, + git_theme, depth + 1, term_width, ); @@ -192,11 +220,13 @@ fn add_header(flags: &Flags, cells: &[Cell], grid: &mut Grid) { } } +#[allow(clippy::too_many_arguments)] fn inner_display_tree( metas: &[Meta], flags: &Flags, colors: &Colors, icons: &Icons, + git_theme: &GitTheme, tree_depth_prefix: (usize, &str), padding_rules: &HashMap<Block, usize>, tree_index: usize, @@ -220,6 +250,7 @@ fn inner_display_tree( meta, colors, icons, + git_theme, flags, &DisplayOption::FileName, padding_rules, @@ -248,6 +279,7 @@ fn inner_display_tree( flags, colors, icons, + git_theme, (tree_depth_prefix.0 + 1, &new_prefix), padding_rules, tree_index, @@ -279,10 +311,12 @@ fn display_folder_path(meta: &Meta) -> String { format!("\n{}:\n", meta.path.to_string_lossy()) } +#[allow(clippy::too_many_arguments)] fn get_output( meta: &Meta, colors: &Colors, icons: &Icons, + git_theme: &GitTheme, flags: &Flags, display_option: &DisplayOption, padding_rules: &HashMap<Block, usize>, @@ -366,6 +400,11 @@ fn get_output( block_vec.push(meta.symlink.render(colors, flags)) } } + Block::GitStatus => { + if let Some(_s) = &meta.git_status { + block_vec.push(_s.render(colors, git_theme)); + } + } }; strings.push( block_vec @@ -457,6 +496,7 @@ mod tests { use assert_fs::prelude::*; use clap::Parser; use std::path::Path; + use tempfile::tempdir; #[test] fn test_display_get_visible_width_without_icons() { @@ -559,8 +599,7 @@ mod tests { // check if the color is present. assert!( output.starts_with("\u{1b}[38;5;"), - "{:?} should start with color", - output, + "{output:?} should start with color" ); assert!(output.ends_with("[39m"), "reset foreground color"); @@ -646,7 +685,7 @@ mod tests { dir.child("one.d/.hidden").touch().unwrap(); let mut metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(42, &flags) + .recurse_into(42, &flags, None) .unwrap() .0 .unwrap(); @@ -656,6 +695,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); assert_eq!("one.d\n├── .hidden\n└── two\n", output); @@ -678,7 +718,7 @@ mod tests { dir.child("dir/file").touch().unwrap(); let metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(42, &flags) + .recurse_into(42, &flags, None) .unwrap() .0 .unwrap(); @@ -687,6 +727,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); let length_before_b = |i| -> usize { @@ -718,7 +759,7 @@ mod tests { dir.child("dir/file").touch().unwrap(); let metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(42, &flags) + .recurse_into(42, &flags, None) .unwrap() .0 .unwrap(); @@ -727,6 +768,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); assert_eq!(output.lines().nth(1).unwrap().chars().next().unwrap(), '└'); @@ -757,7 +799,7 @@ mod tests { dir.child("one.d/two").touch().unwrap(); let metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(42, &flags) + .recurse_into(42, &flags, None) .unwrap() .0 .unwrap(); @@ -766,6 +808,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); assert!(output.ends_with("└── two\n")); @@ -787,7 +830,7 @@ mod tests { dir.child("test").touch().unwrap(); let metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(1, &flags) + .recurse_into(1, &flags, None) .unwrap() .0 .unwrap(); @@ -796,6 +839,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); dir.close().unwrap(); @@ -820,7 +864,7 @@ mod tests { dir.child("testdir").create_dir_all().unwrap(); let metas = Meta::from_path(Path::new(dir.path()), false) .unwrap() - .recurse_into(1, &flags) + .recurse_into(1, &flags, None) .unwrap() .0 .unwrap(); @@ -829,6 +873,7 @@ mod tests { &flags, &Colors::new(color::ThemeOption::NoColor), &Icons::new(false, IconOption::Never, FlagTheme::Fancy, " ".to_string()), + &GitTheme::new(), ); dir.close().unwrap(); @@ -840,4 +885,115 @@ mod tests { assert!(!output.contains("Date Modified")); assert!(!output.contains("Name")); } + + #[test] + fn test_folder_path() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + + let file_path = tmp_dir.path().join("file"); + std::fs::File::create(&file_path).expect("failed to create the file"); + let file = Meta::from_path(&file_path, false).unwrap(); + + let dir_path = tmp_dir.path().join("dir"); + std::fs::create_dir(&dir_path).expect("failed to create the dir"); + let dir = Meta::from_path(&dir_path, false).unwrap(); + + assert_eq!( + display_folder_path(&dir), + format!( + "\n{}{}dir:\n", + tmp_dir.path().to_string_lossy(), + std::path::MAIN_SEPARATOR + ) + ); + + const YES: bool = true; + const NO: bool = false; + + assert_eq!( + should_display_folder_path(0, &[file.clone()], &Flags::default()), + YES // doesn't matter since there is no folder + ); + assert_eq!( + should_display_folder_path(0, &[dir.clone()], &Flags::default()), + NO + ); + assert_eq!( + should_display_folder_path(0, &[file.clone(), dir.clone()], &Flags::default()), + YES + ); + assert_eq!( + should_display_folder_path(0, &[dir.clone(), dir.clone()], &Flags::default()), + YES + ); + assert_eq!( + should_display_folder_path(0, &[file.clone(), file.clone()], &Flags::default()), + YES // doesn't matter since there is no folder + ); + + drop(dir); // to avoid clippy complains about previous .clone() + drop(file); + } + + #[cfg(unix)] + #[test] + fn test_folder_path_with_links() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + + let file_path = tmp_dir.path().join("file"); + std::fs::File::create(&file_path).expect("failed to create the file"); + let file = Meta::from_path(&file_path, false).unwrap(); + + let dir_path = tmp_dir.path().join("dir"); + std::fs::create_dir(&dir_path).expect("failed to create the dir"); + let dir = Meta::from_path(&dir_path, false).unwrap(); + + let link_path = tmp_dir.path().join("link"); + std::os::unix::fs::symlink("dir", &link_path).unwrap(); + let link = Meta::from_path(&link_path, false).unwrap() |