Implement natural sorting on filenames

This commit is contained in:
Ben S 2014-06-01 11:54:31 +01:00
parent 6edf2c0d4d
commit 4722c8991b
4 changed files with 59 additions and 6 deletions

1
exa.rs
View File

@ -14,6 +14,7 @@ pub mod format;
pub mod file;
pub mod unix;
pub mod options;
pub mod sort;
fn main() {
let args = os::args();

13
file.rs
View File

@ -5,6 +5,7 @@ use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
use column::{Column, Permissions, FileName, FileSize, User, Group};
use format::{format_metric_bytes, format_IEC_bytes};
use unix::{get_user_name, get_group_name};
use sort::SortPart;
static MEDIA_TYPES: &'static [&'static str] = &[
"png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
@ -27,6 +28,7 @@ pub struct File<'a> {
pub ext: Option<&'a str>,
pub path: &'a Path,
pub stat: io::FileStat,
pub parts: Vec<SortPart<'a>>,
}
impl<'a> File<'a> {
@ -44,10 +46,11 @@ impl<'a> File<'a> {
};
return File {
path: path,
stat: stat,
name: filename,
ext: File::ext(filename),
path: path,
stat: stat,
name: filename,
ext: File::ext(filename),
parts: SortPart::split_into_parts(filename),
};
}
@ -66,7 +69,7 @@ impl<'a> File<'a> {
pub fn display(&self, column: &Column) -> String {
match *column {
Permissions => self.permissions_string(),
FileName => self.file_colour().paint(self.name.as_slice()),
FileName => self.file_colour().paint(self.name),
FileSize(use_iec) => self.file_size(use_iec),
// Display the ID if the user/group doesn't exist, which

View File

@ -80,7 +80,7 @@ impl Options {
.collect();
match self.sortField {
Name => files.sort_by(|a, b| a.name.cmp(&b.name)),
Name => files.sort_by(|a, b| a.parts.cmp(&b.parts)),
Size => files.sort_by(|a, b| a.stat.size.cmp(&b.stat.size)),
Extension => files.sort_by(|a, b| {
let exts = a.ext.cmp(&b.ext);

49
sort.rs Normal file
View File

@ -0,0 +1,49 @@
// 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, TotalEq, TotalOrd)]
pub enum SortPart<'a> {
Stringular(&'a str),
Numeric(u32),
}
impl<'a> SortPart<'a> {
pub fn from_string(is_digit: bool, slice: &'a str) -> SortPart<'a> {
if is_digit {
Numeric(from_str::<u32>(slice).unwrap())
} else {
Stringular(slice)
}
}
// The logic here is taken from my question at
// http://stackoverflow.com/q/23969191/3484614
pub fn split_into_parts<'a>(input: &'a str) -> Vec<SortPart<'a>> {
let mut parts = vec![];
if input.is_empty() {
return parts
}
let mut is_digit = input.char_at(0).is_digit();
let mut start = 0;
for (i, c) in input.char_indices() {
if is_digit != c.is_digit() {
parts.push(SortPart::from_string(is_digit, input.slice(start, i)));
is_digit = !is_digit;
start = i;
}
}
parts.push(SortPart::from_string(is_digit, input.slice_from(start)));
parts
}
}