Cache the lowercased extension

Extensions aren't ever displayed in lowercase, just compared case-insensitively, so this makes sense.
This commit is contained in:
Ben S 2015-01-26 01:16:19 +00:00
parent 22a4dc90b9
commit da9d1f77d9
3 changed files with 61 additions and 22 deletions

View File

@ -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<String> {
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;

View File

@ -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())
}
}

View File

@ -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<File<'a>>) -> Vec<File<'a>> {
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())
}
}),
}