diff --git a/src/app.rs b/src/app.rs index ae1d33f..9ea99c4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -98,4 +98,14 @@ pub fn build() -> App<'static, 'static> { .multiple(true) .help("Reverse the order of the sort"), ) + .arg( + Arg::with_name("group-dirs") + .long("group-dirs") + .possible_value("none") + .possible_value("first") + .possible_value("last") + .default_value("none") + .multiple(true) + .help("Sort the directories then the files"), + ) } diff --git a/src/batch.rs b/src/batch.rs index d5456b4..2aae33b 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -1,9 +1,8 @@ use ansi_term::{ANSIString, ANSIStrings}; use color::Colors; -use flags::{Flags, SortFlag, SortOrder}; +use flags::{DirOrderFlag, Flags, SortFlag, SortOrder}; use icon::Icons; -use meta::FileType; -use meta::Meta; +use meta::{FileType, Meta}; use std::cmp::Ordering; use std::iter::IntoIterator; use std::vec::IntoIter; @@ -139,98 +138,230 @@ impl Batch { } fn sort_by_meta(a: &Meta, b: &Meta, flags: Flags) -> Ordering { - let ord = match flags.sort_by { - SortFlag::Name => { - if a.file_type == FileType::Directory && b.file_type != FileType::Directory { - Ordering::Less - } else if b.file_type == FileType::Directory && a.file_type != FileType::Directory { - Ordering::Greater - } else { - a.name.cmp(&b.name) - } + match flags.sort_by { + SortFlag::Name => match flags.directory_order { + DirOrderFlag::First => sort_by_name_with_dirs_first(a, b, flags), + DirOrderFlag::None => sort_by_name_with_dirs_unordored(a, b, flags), + DirOrderFlag::Last => sort_by_name_with_files_first(a, b, flags), + }, + + SortFlag::Time => match flags.directory_order { + DirOrderFlag::First => sort_by_date_with_dirs_first(a, b, flags), + DirOrderFlag::None => sort_by_date_with_dirs_unordored(a, b, flags), + DirOrderFlag::Last => sort_by_date_with_files_first(a, b, flags), + }, + } +} + +fn sort_by_name_with_dirs_unordored(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if flags.sort_order == SortOrder::Default { + a.name.cmp(&b.name) + } else { + b.name.cmp(&a.name) + } +} + +fn sort_by_name_with_dirs_first(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if a.file_type == FileType::Directory && b.file_type != FileType::Directory { + Ordering::Less + } else if b.file_type == FileType::Directory && a.file_type != FileType::Directory { + Ordering::Greater + } else { + if flags.sort_order == SortOrder::Default { + a.name.cmp(&b.name) + } else { + b.name.cmp(&a.name) + } + } +} + +fn sort_by_name_with_files_first(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if a.file_type == FileType::Directory && b.file_type != FileType::Directory { + Ordering::Greater + } else if b.file_type == FileType::Directory && a.file_type != FileType::Directory { + Ordering::Less + } else { + if flags.sort_order == SortOrder::Default { + a.name.cmp(&b.name) + } else { + b.name.cmp(&a.name) + } + } +} + +fn sort_by_date_with_dirs_unordored(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if flags.sort_order == SortOrder::Default { + b.date.cmp(&a.date).then(a.name.cmp(&b.name)) + } else { + a.date.cmp(&b.date).then(b.name.cmp(&a.name)) + } +} + +fn sort_by_date_with_dirs_first(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if a.file_type == FileType::Directory && b.file_type != FileType::Directory { + Ordering::Less + } else if b.file_type == FileType::Directory && a.file_type != FileType::Directory { + Ordering::Greater + } else { + if flags.sort_order == SortOrder::Default { + b.date.cmp(&a.date).then(a.name.cmp(&b.name)) + } else { + a.date.cmp(&b.date).then(b.name.cmp(&a.name)) + } + } +} + +fn sort_by_date_with_files_first(a: &Meta, b: &Meta, flags: Flags) -> Ordering { + if a.file_type == FileType::Directory && b.file_type != FileType::Directory { + Ordering::Greater + } else if b.file_type == FileType::Directory && a.file_type != FileType::Directory { + Ordering::Less + } else { + if flags.sort_order == SortOrder::Default { + b.date.cmp(&a.date).then(a.name.cmp(&b.name)) + } else { + a.date.cmp(&b.date).then(b.name.cmp(&a.name)) } - // most recently modified first - SortFlag::Time => b.date.cmp(&a.date).then(a.name.cmp(&b.name)), - }; - match flags.sort_order { - SortOrder::Default => ord, - SortOrder::Reverse => ord.reverse(), } } #[cfg(test)] mod tests { use super::*; + use flags::Flags; use std::fs::{create_dir, File}; use std::process::Command; use tempdir::TempDir; #[test] - fn test_sort_by_meta() { + fn test_sort_by_meta_by_name_with_dirs_first() { let tmp_dir = TempDir::new("test_dir").expect("failed to create temp dir"); - // Create a file and make sure that its mod time is before now. - let path_a = tmp_dir.path().join("a.txt"); + // Create the file; + let path_a = tmp_dir.path().join("zzz"); File::create(&path_a).expect("failed to create file"); - let success = Command::new("touch") - .arg("-t") - .arg("198511160000") - .arg(&path_a) - .status() - .unwrap() - .success(); - assert!(success, "failed to exec touch"); let meta_a = Meta::from_path(&path_a).expect("failed to get meta"); // Create a dir; - let path_b = tmp_dir.path().join("b"); - create_dir(&path_b).expect("failed to create dir"); - let meta_b = Meta::from_path(&path_b).expect("failed to get meta"); + let path_z = tmp_dir.path().join("aaa"); + create_dir(&path_z).expect("failed to create dir"); + let meta_z = Meta::from_path(&path_z).expect("failed to get meta"); - // Sort by name - assert_eq!( - sort_by_meta(&meta_a, &meta_b, Flags::default()), - Ordering::Greater - ); + let mut flags = Flags::default(); + flags.directory_order = DirOrderFlag::First; - // Sort by name reversed - assert_eq!( - sort_by_meta( - &meta_a, - &meta_b, - Flags { - sort_order: SortOrder::Reverse, - ..Flags::default() - } - ), - Ordering::Less - ); + // Sort with the dirs first + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Greater); + + // Sort with the dirs first (the dirs stay first) + flags.sort_order = SortOrder::Reverse; + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Greater); + } + + #[test] + fn test_sort_by_meta_by_name_with_files_first() { + let tmp_dir = TempDir::new("test_dir").expect("failed to create temp dir"); + + // Create the file; + let path_a = tmp_dir.path().join("zzz"); + File::create(&path_a).expect("failed to create file"); + let meta_a = Meta::from_path(&path_a).expect("failed to get meta"); + + // Create a dir; + let path_z = tmp_dir.path().join("aaa"); + create_dir(&path_z).expect("failed to create dir"); + let meta_z = Meta::from_path(&path_z).expect("failed to get meta"); + + let mut flags = Flags::default(); + flags.directory_order = DirOrderFlag::Last; + + // Sort with file first + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Less); + + // Sort with file first reversed (thie files stay first) + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Less); + } + + #[test] + fn test_sort_by_meta_by_name_unordered() { + let tmp_dir = TempDir::new("test_dir").expect("failed to create temp dir"); + + // Create the file; + let path_a = tmp_dir.path().join("aaa"); + File::create(&path_a).expect("failed to create file"); + let meta_a = Meta::from_path(&path_a).expect("failed to get meta"); + + // Create a dir; + let path_z = tmp_dir.path().join("zzz"); + create_dir(&path_z).expect("failed to create dir"); + let meta_z = Meta::from_path(&path_z).expect("failed to get meta"); + + let mut flags = Flags::default(); + flags.directory_order = DirOrderFlag::None; + + // Sort by name unordered + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Less); + + // Sort by name unordered + flags.sort_order = SortOrder::Reverse; + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Greater); + } + + #[test] + fn test_sort_by_meta_by_name_unordered_2() { + let tmp_dir = TempDir::new("test_dir").expect("failed to create temp dir"); + + // Create the file; + let path_a = tmp_dir.path().join("zzz"); + File::create(&path_a).expect("failed to create file"); + let meta_a = Meta::from_path(&path_a).expect("failed to get meta"); + + // Create a dir; + let path_z = tmp_dir.path().join("aaa"); + create_dir(&path_z).expect("failed to create dir"); + let meta_z = Meta::from_path(&path_z).expect("failed to get meta"); + + let mut flags = Flags::default(); + flags.directory_order = DirOrderFlag::None; + + // Sort by name unordered + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Greater); + + // Sort by name unordered reversed + flags.sort_order = SortOrder::Reverse; + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Less); + } + + #[test] + fn test_sort_by_meta_by_time() { + let tmp_dir = TempDir::new("test_dir").expect("failed to create temp dir"); + + // Create the file; + let path_a = tmp_dir.path().join("aaa"); + File::create(&path_a).expect("failed to create file"); + let meta_a = Meta::from_path(&path_a).expect("failed to get meta"); + + // Create the file; + let path_z = tmp_dir.path().join("zzz"); + File::create(&path_z).expect("failed to create file"); + let success = Command::new("touch") + .arg("-t") + .arg("198511160000") + .arg(&path_z) + .status() + .unwrap() + .success(); + assert_eq!(true, success, "failed to exec mkfifo"); + let meta_z = Meta::from_path(&path_z).expect("failed to get meta"); + + let mut flags = Flags::default(); + flags.sort_by = SortFlag::Time; // Sort by time - assert_eq!( - sort_by_meta( - &meta_a, - &meta_b, - Flags { - sort_by: SortFlag::Time, - ..Flags::default() - } - ), - Ordering::Greater - ); + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Less); // Sort by time reversed - assert_eq!( - sort_by_meta( - &meta_a, - &meta_b, - Flags { - sort_by: SortFlag::Time, - sort_order: SortOrder::Reverse, - ..Flags::default() - } - ), - Ordering::Less - ); + flags.sort_order = SortOrder::Reverse; + assert_eq!(sort_by_meta(&meta_a, &meta_z, flags), Ordering::Greater); } } diff --git a/src/flags.rs b/src/flags.rs index f764dae..f1fb675 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -10,6 +10,7 @@ pub struct Flags { pub recursive: bool, pub sort_by: SortFlag, pub sort_order: SortOrder, + pub directory_order: DirOrderFlag, pub date: DateFlag, pub color: WhenFlag, pub icon: WhenFlag, @@ -21,6 +22,7 @@ impl Flags { let color_inputs: Vec<&str> = matches.values_of("color").unwrap().collect(); let icon_inputs: Vec<&str> = matches.values_of("icon").unwrap().collect(); let date_inputs: Vec<&str> = matches.values_of("date").unwrap().collect(); + let dir_order_inputs: Vec<&str> = matches.values_of("group-dirs").unwrap().collect(); let sort_by = if matches.is_present("timesort") { SortFlag::Time @@ -68,6 +70,7 @@ impl Flags { date: DateFlag::from(date_inputs[date_inputs.len() - 1]), color: WhenFlag::from(color_inputs[color_inputs.len() - 1]), icon: WhenFlag::from(icon_inputs[icon_inputs.len() - 1]), + directory_order: DirOrderFlag::from(dir_order_inputs[dir_order_inputs.len() - 1]), }) } } @@ -84,6 +87,7 @@ impl Default for Flags { recursion_depth: usize::max_value(), sort_by: SortFlag::Name, sort_order: SortOrder::Default, + directory_order: DirOrderFlag::None, date: DateFlag::Date, color: WhenFlag::Auto, icon: WhenFlag::Auto, @@ -137,6 +141,24 @@ pub enum SortOrder { Reverse, } +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub enum DirOrderFlag { + None, + First, + Last, +} + +impl<'a> From<&'a str> for DirOrderFlag { + fn from(when: &'a str) -> Self { + match when { + "none" => DirOrderFlag::None, + "first" => DirOrderFlag::First, + "last" => DirOrderFlag::Last, + _ => panic!("invalid \"when\" flag: {}", when), + } + } +} + #[cfg(test)] mod test { use super::Flags; diff --git a/src/meta/indicator.rs b/src/meta/indicator.rs index 2c573da..f1129a4 100644 --- a/src/meta/indicator.rs +++ b/src/meta/indicator.rs @@ -34,7 +34,7 @@ impl Indicator { #[cfg(test)] mod test { use super::Indicator; - use flags::{DateFlag, Flags, SortFlag, SortOrder, WhenFlag}; + use flags::Flags; use meta::FileType; #[test] @@ -69,20 +69,8 @@ mod test { #[test] fn test_symlink_indicator() { - let flags = Flags { - display_all: true, - display_long: true, - display_online: true, - display_tree: true, - display_indicators: true, - recursive: true, - recursion_depth: usize::max_value(), - sort_by: SortFlag::Name, - sort_order: SortOrder::Default, - date: DateFlag::Relative, - color: WhenFlag::Always, - icon: WhenFlag::Always, - }; + let mut flags = Flags::default(); + flags.display_indicators = true; let file_type = Indicator::from(FileType::SymLink);