diff --git a/src/file.rs b/src/file.rs index a7b58d2d6479838fe8e5fccc01f23da777c51357..9982494fd5e7c2e03459ca518b8d8422bdced1ec 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,5 +1,6 @@ use std::io::{fs, IoResult}; use std::io; +use std::ascii::AsciiExt; use ansi_term::{ANSIString, Colour, Style}; use ansi_term::Style::Plain; @@ -370,16 +371,20 @@ impl<'a> File<'a> { } } -/// Extract an extension from a string, if one is present. +/// Extract an extension from a string, if one is present, in lowercase. /// /// The extension is the series of characters after the last dot. This /// deliberately counts dotfiles, so the ".git" folder has the extension "git". +/// +/// ASCII lowercasing is used because these extensions are only compared +/// against a pre-compiled list of extensions which are known to only exist +/// within ASCII, so it's alright. fn ext<'a>(name: &'a str) -> Option { - name.rfind('.').map(|p| name[p+1..].to_string()) + name.rfind('.').map(|p| name[p+1..].to_ascii_lowercase()) } #[cfg(test)] -mod test { +pub mod test { pub use super::*; pub use column::{Cell, Column}; pub use std::io; diff --git a/src/filetype.rs b/src/filetype.rs index 81a60954d254d928650718ee1da7bc056b2c1f16..16922d2a6fbca6855c6970669dfda6a172123df8 100644 --- a/src/filetype.rs +++ b/src/filetype.rs @@ -2,13 +2,12 @@ use file::{File, GREY}; use self::FileType::*; use std::io; -use std::ascii::AsciiExt; use ansi_term::Style; use ansi_term::Style::Plain; use ansi_term::Colour::{Red, Green, Yellow, Blue, Cyan, Fixed}; -#[derive(Copy)] +#[derive(PartialEq, Debug, Copy)] pub enum FileType { Normal, Directory, Executable, Immediate, Compiled, Symlink, Special, Image, Video, Music, Lossless, Compressed, Document, Temp, Crypto, @@ -100,30 +99,29 @@ impl<'a> HasType for File<'a> { else if name.starts_with("README") || BUILD_TYPES.iter().any(|&s| s == name) { return Immediate; } - else if let Some(ref e) = self.ext { - let ext = e.as_slice().to_ascii_lowercase(); - if IMAGE_TYPES.iter().any(|&s| s == ext) { + else if let Some(ref ext) = self.ext { + if IMAGE_TYPES.iter().any(|&s| s == *ext) { return Image; } - else if VIDEO_TYPES.iter().any(|&s| s == ext) { + else if VIDEO_TYPES.iter().any(|&s| s == *ext) { return Video; } - else if MUSIC_TYPES.iter().any(|&s| s == ext) { + else if MUSIC_TYPES.iter().any(|&s| s == *ext) { return Music; } - else if MUSIC_LOSSLESS.iter().any(|&s| s == ext) { + else if MUSIC_LOSSLESS.iter().any(|&s| s == *ext) { return Lossless; } - else if CRYPTO_TYPES.iter().any(|&s| s == ext) { + else if CRYPTO_TYPES.iter().any(|&s| s == *ext) { return Crypto; } - else if DOCUMENT_TYPES.iter().any(|&s| s == ext) { + else if DOCUMENT_TYPES.iter().any(|&s| s == *ext) { return Document; } - else if COMPRESSED_TYPES.iter().any(|&s| s == ext) { + else if COMPRESSED_TYPES.iter().any(|&s| s == *ext) { return Compressed; } - else if self.is_tmpfile() || TEMP_TYPES.iter().any(|&s| s == ext) { + else if self.is_tmpfile() || TEMP_TYPES.iter().any(|&s| s == *ext) { return Temp; } @@ -135,7 +133,7 @@ impl<'a> HasType for File<'a> { return Temp; } else { - if COMPILED_TYPES.iter().any(|&s| s == ext) { + if COMPILED_TYPES.iter().any(|&s| s == *ext) { return Compiled; } else { @@ -147,3 +145,35 @@ impl<'a> HasType for File<'a> { return Normal; // no filetype } } + +#[cfg(test)] +mod test { + use super::*; + use file::File; + use file::test::dummy_stat; + + #[test] + fn lowercase() { + let file = File::with_stat(dummy_stat(), &Path::new("/barracks.wav"), None); + assert_eq!(FileType::Lossless, file.get_type()) + } + + #[test] + fn uppercase() { + let file = File::with_stat(dummy_stat(), &Path::new("/BARRACKS.WAV"), None); + assert_eq!(FileType::Lossless, file.get_type()) + } + + #[test] + fn cargo() { + let file = File::with_stat(dummy_stat(), &Path::new("/Cargo.toml"), None); + assert_eq!(FileType::Immediate, file.get_type()) + } + + #[test] + fn not_cargo() { + let file = File::with_stat(dummy_stat(), &Path::new("/cargo.toml"), None); + assert_eq!(FileType::Normal, file.get_type()) + } + +} diff --git a/src/options.rs b/src/options.rs index e6258335400672f61a629fcec828d1cb90f5dd53..2cb45e41573ee863de9c64306079875d2fbd72b4 100644 --- a/src/options.rs +++ b/src/options.rs @@ -8,8 +8,9 @@ use output::View; use term::dimensions; use std::ascii::AsciiExt; -use std::slice::Iter; +use std::cmp::Ordering; use std::fmt; +use std::slice::Iter; use self::Misfire::*; @@ -81,7 +82,7 @@ impl Options { self.view.view(files) } - /// Transform the files somehow before listing them. + /// Transform the files (sorting, reversing, filtering) before listing them. pub fn transform_files<'a>(&self, mut files: Vec>) -> Vec> { if !self.show_invisibles { @@ -90,13 +91,16 @@ impl Options { match self.sort_field { SortField::Unsorted => {}, - SortField::Name => files.sort_by(|a, b| natord::compare(a.name.as_slice(), b.name.as_slice())), + SortField::Name => files.sort_by(|a, b| natord::compare(&*a.name, &*b.name)), 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| { - let exts = a.ext.clone().map(|e| e.to_ascii_lowercase()).cmp(&b.ext.clone().map(|e| e.to_ascii_lowercase())); - let names = a.name.to_ascii_lowercase().cmp(&b.name.to_ascii_lowercase()); - exts.cmp(&names) + if a.ext.cmp(&b.ext) == Ordering::Equal { + Ordering::Equal + } + else { + a.name.to_ascii_lowercase().cmp(&b.name.to_ascii_lowercase()) + } }), }