2014-05-04 20:33:14 +00:00
|
|
|
use std::io::fs;
|
|
|
|
use std::io;
|
|
|
|
|
|
|
|
use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
|
2014-05-05 10:29:50 +00:00
|
|
|
use column::{Column, Permissions, FileName, FileSize, User, Group};
|
2014-05-04 20:33:14 +00:00
|
|
|
use format::{formatBinaryBytes, formatDecimalBytes};
|
2014-05-05 10:29:50 +00:00
|
|
|
use unix::{get_user_name, get_group_name};
|
2014-05-04 20:33:14 +00:00
|
|
|
|
2014-05-24 19:40:31 +00:00
|
|
|
static MEDIA_TYPES: &'static [&'static str] = &[
|
|
|
|
"png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
|
|
|
|
"ppm", "pgm", "pbm", "pnm", "webp", "raw", "arw",
|
|
|
|
"svg", "pdf", "stl", "eps", "dvi", "ps" ];
|
|
|
|
|
|
|
|
static COMPRESSED_TYPES: &'static [&'static str] = &[
|
|
|
|
"zip", "tar", "Z", "gz", "bz2", "a", "ar", "7z",
|
|
|
|
"iso", "dmg", "tc", "rar", "par" ];
|
|
|
|
|
2014-05-04 20:33:14 +00:00
|
|
|
// Each file is definitely going to get `stat`ted at least once, if
|
|
|
|
// only to determine what kind of file it is, so carry the `stat`
|
|
|
|
// result around with the file for safe keeping.
|
|
|
|
pub struct File<'a> {
|
2014-05-24 01:17:43 +00:00
|
|
|
pub name: &'a str,
|
2014-05-24 01:36:00 +00:00
|
|
|
pub ext: Option<&'a str>,
|
2014-05-24 01:17:43 +00:00
|
|
|
pub path: &'a Path,
|
|
|
|
pub stat: io::FileStat,
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> File<'a> {
|
|
|
|
pub fn from_path(path: &'a Path) -> File<'a> {
|
|
|
|
let filename: &str = path.filename_str().unwrap();
|
|
|
|
|
|
|
|
// We have to use lstat here instad of file.stat(), as it
|
|
|
|
// doesn't follow symbolic links. Otherwise, the stat() call
|
|
|
|
// will fail if it encounters a link that's target is
|
|
|
|
// non-existent.
|
|
|
|
let stat: io::FileStat = match fs::lstat(path) {
|
|
|
|
Ok(stat) => stat,
|
|
|
|
Err(e) => fail!("Couldn't stat {}: {}", filename, e),
|
|
|
|
};
|
|
|
|
|
2014-05-24 01:36:00 +00:00
|
|
|
return File {
|
|
|
|
path: path,
|
|
|
|
stat: stat,
|
|
|
|
name: filename,
|
|
|
|
ext: File::ext(filename),
|
|
|
|
};
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
|
2014-05-24 01:36:00 +00:00
|
|
|
fn ext(name: &'a str) -> Option<&'a str> {
|
2014-05-24 01:32:57 +00:00
|
|
|
let re = regex!(r"\.(.+)$");
|
2014-05-24 01:36:00 +00:00
|
|
|
re.captures(name).map(|caps| caps.at(1))
|
2014-05-24 01:32:57 +00:00
|
|
|
}
|
|
|
|
|
2014-05-05 09:51:24 +00:00
|
|
|
pub fn is_dotfile(&self) -> bool {
|
|
|
|
self.name.starts_with(".")
|
|
|
|
}
|
|
|
|
|
2014-05-26 10:08:33 +00:00
|
|
|
pub fn display(&self, column: &Column) -> String {
|
2014-05-04 20:33:14 +00:00
|
|
|
match *column {
|
|
|
|
Permissions => self.permissions(),
|
2014-05-24 00:14:40 +00:00
|
|
|
FileName => self.file_colour().paint(self.name.as_slice()),
|
2014-05-04 20:33:14 +00:00
|
|
|
FileSize(si) => self.file_size(si),
|
2014-05-22 00:18:39 +00:00
|
|
|
User => get_user_name(self.stat.unstable.uid as i32).unwrap_or(self.stat.unstable.uid.to_str()),
|
|
|
|
Group => get_group_name(self.stat.unstable.gid as u32).unwrap_or(self.stat.unstable.gid.to_str()),
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-26 10:08:33 +00:00
|
|
|
fn file_size(&self, si: bool) -> String {
|
2014-05-25 14:52:36 +00:00
|
|
|
// Don't report file sizes for directories. I've never looked
|
|
|
|
// at one of those numbers and gained any information from it.
|
|
|
|
if self.stat.kind == io::TypeDirectory {
|
|
|
|
Black.bold().paint("---")
|
2014-05-04 20:33:14 +00:00
|
|
|
} else {
|
2014-05-25 14:52:36 +00:00
|
|
|
let sizeStr = if si {
|
|
|
|
formatBinaryBytes(self.stat.size)
|
|
|
|
} else {
|
|
|
|
formatDecimalBytes(self.stat.size)
|
|
|
|
};
|
2014-05-04 20:33:14 +00:00
|
|
|
|
2014-05-25 14:52:36 +00:00
|
|
|
return Green.bold().paint(sizeStr.as_slice());
|
|
|
|
}
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
|
2014-05-26 10:08:33 +00:00
|
|
|
fn type_char(&self) -> String {
|
2014-05-04 20:33:14 +00:00
|
|
|
return match self.stat.kind {
|
2014-05-24 00:14:40 +00:00
|
|
|
io::TypeFile => ".".to_strbuf(),
|
2014-05-04 20:33:14 +00:00
|
|
|
io::TypeDirectory => Blue.paint("d"),
|
|
|
|
io::TypeNamedPipe => Yellow.paint("|"),
|
|
|
|
io::TypeBlockSpecial => Purple.paint("s"),
|
|
|
|
io::TypeSymlink => Cyan.paint("l"),
|
2014-05-05 09:51:24 +00:00
|
|
|
_ => "?".to_owned(),
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn file_colour(&self) -> Style {
|
|
|
|
if self.stat.kind == io::TypeDirectory {
|
|
|
|
Blue.normal()
|
2014-05-24 19:40:31 +00:00
|
|
|
}
|
|
|
|
else if self.stat.perm.contains(io::UserExecute) {
|
|
|
|
Green.bold()
|
|
|
|
}
|
|
|
|
else if self.name.ends_with("~") {
|
2014-05-04 20:33:14 +00:00
|
|
|
Black.bold()
|
2014-05-24 19:40:31 +00:00
|
|
|
}
|
|
|
|
else if self.name.starts_with("README") {
|
|
|
|
Yellow.bold().underline()
|
|
|
|
}
|
|
|
|
else if self.ext.is_some() && MEDIA_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
|
|
|
|
Purple.normal()
|
|
|
|
}
|
|
|
|
else if self.ext.is_some() && COMPRESSED_TYPES.iter().any(|&s| s == self.ext.unwrap()) {
|
|
|
|
Red.normal()
|
|
|
|
}
|
|
|
|
else {
|
2014-05-04 20:33:14 +00:00
|
|
|
Plain
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-26 10:08:33 +00:00
|
|
|
fn permissions(&self) -> String {
|
2014-05-04 20:33:14 +00:00
|
|
|
let bits = self.stat.perm;
|
|
|
|
return format!("{}{}{}{}{}{}{}{}{}{}",
|
|
|
|
self.type_char(),
|
2014-05-26 10:50:46 +00:00
|
|
|
File::bit(bits, io::UserRead, "r", Yellow.bold()),
|
|
|
|
File::bit(bits, io::UserWrite, "w", Red.bold()),
|
|
|
|
File::bit(bits, io::UserExecute, "x", Green.bold().underline()),
|
|
|
|
File::bit(bits, io::GroupRead, "r", Yellow.normal()),
|
|
|
|
File::bit(bits, io::GroupWrite, "w", Red.normal()),
|
|
|
|
File::bit(bits, io::GroupExecute, "x", Green.normal()),
|
|
|
|
File::bit(bits, io::OtherRead, "r", Yellow.normal()),
|
|
|
|
File::bit(bits, io::OtherWrite, "w", Red.normal()),
|
|
|
|
File::bit(bits, io::OtherExecute, "x", Green.normal()),
|
2014-05-04 20:33:14 +00:00
|
|
|
);
|
|
|
|
}
|
2014-05-26 10:50:46 +00:00
|
|
|
|
|
|
|
fn bit(bits: io::FilePermission, bit: io::FilePermission, other: &'static str, style: Style) -> String {
|
|
|
|
if bits.contains(bit) {
|
|
|
|
style.paint(other.as_slice())
|
|
|
|
} else {
|
|
|
|
Black.bold().paint("-".as_slice())
|
|
|
|
}
|
|
|
|
}
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
|
2014-05-26 10:50:46 +00:00
|
|
|
impl<'a> Clone for File<'a> {
|
|
|
|
fn clone(&self) -> File<'a> {
|
|
|
|
return File {
|
|
|
|
path: self.path,
|
|
|
|
stat: self.stat,
|
|
|
|
name: self.name.clone(),
|
|
|
|
ext: self.ext.clone(),
|
|
|
|
};
|
2014-05-04 20:33:14 +00:00
|
|
|
}
|
|
|
|
}
|