diff --git a/Cargo.lock b/Cargo.lock index eddc5e5..c835005 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,11 @@ dependencies = [ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "human-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ignore" version = "0.4.14" @@ -264,6 +269,7 @@ dependencies = [ "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "human-sort 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)", "lscolors 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "predicates 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -609,6 +615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum globset 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120" "checksum globwalk 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d9db17aec586697a93219b19726b5b68307eba92898c34b170857343fe67c99d" "checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" +"checksum human-sort 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" "checksum ignore 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf60d063dbe6b75388eec66cfc07781167ae3d34a09e0c433e6c5de0511f7fb" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" diff --git a/Cargo.toml b/Cargo.toml index 42e0239..2ebcccf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ unicode-width = "0.1.*" lscolors = "0.7" wild = "2.0.*" globset = "0.4.*" +human-sort = "0.2.2" [target.'cfg(unix)'.dependencies] users = "0.10.*" diff --git a/src/app.rs b/src/app.rs index a446ce6..e6a4fb7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -146,6 +146,7 @@ pub fn build() -> App<'static, 'static> { .long("timesort") .overrides_with("sizesort") .overrides_with("extensionsort") + .overrides_with("versionsort") .multiple(true) .help("Sort by time modified"), ) @@ -155,6 +156,7 @@ pub fn build() -> App<'static, 'static> { .long("sizesort") .overrides_with("timesort") .overrides_with("extensionsort") + .overrides_with("versionsort") .multiple(true) .help("Sort by size"), ) @@ -164,9 +166,19 @@ pub fn build() -> App<'static, 'static> { .long("extensionsort") .overrides_with("sizesort") .overrides_with("timesort") + .overrides_with("versionsort") .multiple(true) .help("Sort by file extension"), ) + .arg( + Arg::with_name("versionsort") + .short("v") + .multiple(true) + .overrides_with("timesort") + .overrides_with("sizesort") + .overrides_with("extensionsort") + .help("Natural sort of (version) numbers within text"), + ) .arg( Arg::with_name("reverse") .short("r") diff --git a/src/flags.rs b/src/flags.rs index 473b590..fc430b2 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -60,6 +60,8 @@ impl Flags { SortFlag::Size } else if matches.is_present("extensionsort") { SortFlag::Extension + } else if matches.is_present("versionsort") { + SortFlag::Version } else { SortFlag::Name }; @@ -308,6 +310,7 @@ pub enum SortFlag { Name, Time, Size, + Version, Extension, } diff --git a/src/meta/filetype.rs b/src/meta/filetype.rs index 9094803..e7c275f 100644 --- a/src/meta/filetype.rs +++ b/src/meta/filetype.rs @@ -81,10 +81,7 @@ impl FileType { } pub fn is_dirlike(self) -> bool { - match self { - FileType::Directory { .. } | FileType::SymLink { is_dir: true } => true, - _ => false, - } + matches!(self, FileType::Directory { .. } | FileType::SymLink { is_dir: true }) } } diff --git a/src/sort.rs b/src/sort.rs index ea5defa..ab72929 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,5 +1,6 @@ use crate::flags::{DirOrderFlag, Flags, SortFlag, SortOrder}; use crate::meta::Meta; +use human_sort::compare; use std::cmp::Ordering; pub type SortFn = fn(&Meta, &Meta) -> Ordering; @@ -19,6 +20,7 @@ pub fn assemble_sorters(flags: &Flags) -> Vec<(SortOrder, SortFn)> { SortFlag::Name => by_name, SortFlag::Size => by_size, SortFlag::Time => by_date, + SortFlag::Version => by_version, SortFlag::Extension => by_extension, }; sorters.push((flags.sort_order, other_sort)); @@ -56,6 +58,10 @@ fn by_date(a: &Meta, b: &Meta) -> Ordering { b.date.cmp(&a.date).then(a.name.cmp(&b.name)) } +fn by_version(a: &Meta, b: &Meta) -> Ordering { + compare(&a.name.name, &b.name.name) +} + fn by_extension(a: &Meta, b: &Meta) -> Ordering { a.name.extension().cmp(&b.name.extension()) } @@ -263,4 +269,30 @@ mod tests { let sorter = assemble_sorters(&flags); assert_eq!(by_meta(&sorter, &meta_a, &meta_t), Ordering::Less); } + + #[test] + fn test_sort_assemble_sorters_by_version() { + let tmp_dir = tempdir().expect("failed to create temp dir"); + + let path_a = tmp_dir.path().join("2"); + File::create(&path_a).expect("failed to create file"); + let meta_a = Meta::from_path(&path_a, false).expect("failed to get meta"); + + let path_b = tmp_dir.path().join("11"); + File::create(&path_b).expect("failed to create file"); + let meta_b = Meta::from_path(&path_b, false).expect("failed to get meta"); + + let path_c = tmp_dir.path().join("12"); + File::create(&path_c).expect("failed to create file"); + let meta_c = Meta::from_path(&path_c, false).expect("failed to get meta"); + + let mut flags = Flags::default(); + flags.sort_by = SortFlag::Version; + + let sorter = assemble_sorters(&flags); + assert_eq!(by_meta(&sorter, &meta_b, &meta_a), Ordering::Greater); + + let sorter = assemble_sorters(&flags); + assert_eq!(by_meta(&sorter, &meta_b, &meta_c), Ordering::Less); + } } diff --git a/tests/integration.rs b/tests/integration.rs index 554c72b..4fb81e8 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -260,6 +260,53 @@ fn test_show_folder_of_symlink_for_long_multi() { .stdout(predicate::str::contains("link/:")); } +#[test] +fn test_version_sort() { + let dir = tempdir(); + dir.child("0.3.7").touch().unwrap(); + dir.child("0.11.5").touch().unwrap(); + dir.child("11a").touch().unwrap(); + dir.child("0.2").touch().unwrap(); + dir.child("0.11").touch().unwrap(); + dir.child("1").touch().unwrap(); + dir.child("11").touch().unwrap(); + dir.child("2").touch().unwrap(); + dir.child("22").touch().unwrap(); + cmd().arg("-v").arg(dir.path()).assert().stdout( + predicate::str::is_match("0.2\n0.3.7\n0.11\n0.11.5\n1\n2\n11\n11a\n22\n$").unwrap(), + ); +} + +#[test] +fn test_version_sort_overwrite_by_timesort() { + let dir = tempdir(); + dir.child("2").touch().unwrap(); + dir.child("11").touch().unwrap(); + cmd() + .arg("-v") + .arg("-t") + .arg(dir.path()) + .assert() + .stdout(predicate::str::is_match("11\n2\n$").unwrap()); +} + +#[test] +fn test_version_sort_overwrite_by_sizesort() { + use std::fs::File; + use std::io::Write; + let dir = tempdir(); + dir.child("2").touch().unwrap(); + let larger = dir.path().join("11"); + let mut larger_file = File::create(larger).unwrap(); + writeln!(larger_file, "this is larger").unwrap(); + cmd() + .arg("-v") + .arg("-S") + .arg(dir.path()) + .assert() + .stdout(predicate::str::is_match("11\n2\n$").unwrap()); +} + fn cmd() -> Command { Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap() }