diff --git a/Cargo.lock b/Cargo.lock index 0b09877..e223f44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,7 @@ name = "exa" version = "0.1.0" dependencies = [ "ansi_term 0.3.0 (git+https://github.com/ogham/rust-ansi-term.git)", + "natord 1.0.0 (git+https://github.com/lifthrasiir/rust-natord.git)", "users 0.1.0 (git+https://github.com/ogham/rust-users)", ] @@ -11,6 +12,11 @@ name = "ansi_term" version = "0.3.0" source = "git+https://github.com/ogham/rust-ansi-term.git#4b9ea6cf266053e1a771e75b935b4e54c586c139" +[[package]] +name = "natord" +version = "1.0.0" +source = "git+https://github.com/lifthrasiir/rust-natord.git#83ebf6e7999fe2646bca45d5f6800728a0bbd5c5" + [[package]] name = "users" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 97c434b..0cd0744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ name = "exa" git = "https://github.com/ogham/rust-ansi-term.git" [dependencies.users] -git = "https://github.com/ogham/rust-users.git" \ No newline at end of file +git = "https://github.com/ogham/rust-users.git" + +[dependencies.natord] +git = "https://github.com/lifthrasiir/rust-natord.git" \ No newline at end of file diff --git a/src/exa.rs b/src/exa.rs index 86f3043..44a735a 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -28,7 +28,6 @@ pub mod format; pub mod file; pub mod filetype; pub mod options; -pub mod sort; pub mod term; fn main() { diff --git a/src/file.rs b/src/file.rs index ae61ff0..cb91ae1 100644 --- a/src/file.rs +++ b/src/file.rs @@ -10,7 +10,6 @@ use users::{Users, OSUsers}; use column::Column; use column::Column::*; use format::{format_metric_bytes, format_IEC_bytes}; -use sort::SortPart; use dir::Dir; use filetype::HasType; @@ -29,7 +28,6 @@ pub struct File<'a> { pub ext: Option, pub path: Path, pub stat: io::FileStat, - pub parts: Vec, } impl<'a> File<'a> { @@ -50,7 +48,6 @@ impl<'a> File<'a> { stat: stat, name: filename.clone(), ext: File::ext(filename.clone()), - parts: SortPart::split_into_parts(filename.clone()), } } @@ -210,7 +207,6 @@ impl<'a> File<'a> { stat: stat, name: filename.clone(), ext: File::ext(filename.clone()), - parts: vec![], // not needed }); // Statting a path usually fails because the file at the diff --git a/src/options.rs b/src/options.rs index d86d9b5..e7cb2dc 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,5 @@ extern crate getopts; +extern crate natord; use file::File; use column::Column; @@ -46,7 +47,7 @@ impl Options { getopts::optflag("1", "oneline", "display one entry per line"), getopts::optflag("a", "all", "show dot-files"), getopts::optflag("b", "binary", "use binary prefixes in file sizes"), - getopts::optflag("d", "list-dirs", "list directories as regular files"), + getopts::optflag("d", "list-dirs", "list directories as regular files"), getopts::optflag("g", "group", "show group as well as user"), getopts::optflag("h", "header", "show a header row at the top"), getopts::optflag("H", "links", "show number of hard links"), @@ -60,27 +61,27 @@ impl Options { ]; let matches = match getopts::getopts(args.tail(), &opts) { - Ok(m) => m, - Err(e) => { - println!("Invalid options: {}", e); - return Err(1); - } + Ok(m) => m, + Err(e) => { + println!("Invalid options: {}", e); + return Err(1); + } }; if matches.opt_present("help") { - println!("exa - ls with more features\n\n{}", getopts::usage("Usage:\n exa [options] [files...]", &opts)) - return Err(2); + println!("exa - ls with more features\n\n{}", getopts::usage("Usage:\n exa [options] [files...]", &opts)) + return Err(2); } Ok(Options { - header: matches.opt_present("header"), - list_dirs: matches.opt_present("list-dirs"), - path_strs: if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() }, - reverse: matches.opt_present("reverse"), - show_invisibles: matches.opt_present("all"), - sort_field: matches.opt_str("sort").map(|word| SortField::from_word(word)).unwrap_or(SortField::Name), - view: Options::view(&matches), - }) + header: matches.opt_present("header"), + list_dirs: matches.opt_present("list-dirs"), + path_strs: if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() }, + reverse: matches.opt_present("reverse"), + show_invisibles: matches.opt_present("all"), + sort_field: matches.opt_str("sort").map(|word| SortField::from_word(word)).unwrap_or(SortField::Name), + view: Options::view(&matches), + }) } fn view(matches: &getopts::Matches) -> View { @@ -124,7 +125,7 @@ impl Options { } columns.push(FileName); - columns + columns } fn should_display(&self, f: &File) -> bool { @@ -143,7 +144,7 @@ impl Options { match self.sort_field { SortField::Unsorted => {}, - SortField::Name => files.sort_by(|a, b| a.parts.cmp(&b.parts)), + SortField::Name => files.sort_by(|a, b| natord::compare(a.name.as_slice(), b.name.as_slice())), SortField::Size => files.sort_by(|a, b| a.stat.size.cmp(&b.stat.size)), SortField::FileInode => files.sort_by(|a, b| a.stat.unstable.inode.cmp(&b.stat.unstable.inode)), SortField::Extension => files.sort_by(|a, b| { diff --git a/src/sort.rs b/src/sort.rs deleted file mode 100644 index 4449fd8..0000000 --- a/src/sort.rs +++ /dev/null @@ -1,100 +0,0 @@ -use self::SortPart::*; -use std::ascii::AsciiExt; - -// This is an implementation of "natural sort order". See -// http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/ -// for more information and examples. It tries to sort "9" before -// "10", which makes sense to those regular human types. - -// It works by splitting an input string into several parts, and then -// comparing based on those parts. A SortPart derives TotalOrd, so a -// Vec will automatically have natural sorting. - -#[deriving(Eq, Ord, PartialEq, PartialOrd)] -pub enum SortPart { - Numeric(u64), - Stringular(String), -} - -impl SortPart { - fn from_string(is_digit: bool, slice: &str) -> SortPart { - if is_digit { - // numbers too big for a u64 fall back into strings. - match from_str::(slice) { - Some(num) => Numeric(num), - None => Stringular(slice.to_string()), - } - } - else { - SortPart::Stringular(slice.to_ascii_lower()) - } - } - - // The logic here is taken from my question at - // http://stackoverflow.com/q/23969191/3484614 - - pub fn split_into_parts(input: String) -> Vec { - let mut parts = vec![]; - - if input.is_empty() { - return parts - } - - let mut is_digit = input.as_slice().char_at(0).is_digit(10); - let mut start = 0; - - for (i, c) in input.as_slice().char_indices() { - if is_digit != c.is_digit(10) { - parts.push(SortPart::from_string(is_digit, input.as_slice().slice(start, i))); - is_digit = !is_digit; - start = i; - } - } - - parts.push(SortPart::from_string(is_digit, input.as_slice().slice_from(start))); - parts - } -} - -#[test] -fn test_numeric() { - let bits = SortPart::split_into_parts("123456789".to_string()); - assert!(bits == vec![ Numeric(123456789) ]); -} - - -#[test] -fn test_stringular() { - let bits = SortPart::split_into_parts("toothpaste".to_string()); - assert!(bits == vec![ Stringular("toothpaste".to_string()) ]); -} - -#[test] -fn test_empty() { - let bits = SortPart::split_into_parts("".to_string()); - assert!(bits == vec![]); -} - -#[test] -fn test_one() { - let bits = SortPart::split_into_parts("123abc123".to_string()); - assert!(bits == vec![ Numeric(123), Stringular("abc".to_string()), Numeric(123) ]); -} - -#[test] -fn test_two() { - let bits = SortPart::split_into_parts("final version 3.pdf".to_string()); - assert!(bits == vec![ Stringular("final version ".to_string()), Numeric(3), Stringular(".pdf".to_string()) ]); -} - -#[test] -fn test_huge_number() { - let bits = SortPart::split_into_parts("9999999999999999999999999999999999999999999999999999999".to_string()); - assert!(bits == vec![ Stringular("9999999999999999999999999999999999999999999999999999999".to_string()) ]); -} - -#[test] -fn test_case() { - let bits = SortPart::split_into_parts("123ABC123".to_string()); - assert!(bits == vec![ Numeric(123), Stringular("abc".to_string()), Numeric(123) ]); -}