mirror of https://github.com/Llewellynvdm/exa.git
Use the natord library instead of our own sorter
I'm serious, the more functionality I can use external crates for, the better.
This commit is contained in:
parent
6770ac5475
commit
65f124fe39
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
git = "https://github.com/ogham/rust-users.git"
|
||||
|
||||
[dependencies.natord]
|
||||
git = "https://github.com/lifthrasiir/rust-natord.git"
|
|
@ -28,7 +28,6 @@ pub mod format;
|
|||
pub mod file;
|
||||
pub mod filetype;
|
||||
pub mod options;
|
||||
pub mod sort;
|
||||
pub mod term;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -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<String>,
|
||||
pub path: Path,
|
||||
pub stat: io::FileStat,
|
||||
pub parts: Vec<SortPart>,
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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| {
|
||||
|
|
100
src/sort.rs
100
src/sort.rs
|
@ -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<SortPart> 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::<u64>(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<SortPart> {
|
||||
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) ]);
|
||||
}
|
Loading…
Reference in New Issue