file.rs 4.8 KB
Newer Older
B
Ben S 已提交
1 2 3 4
use std::io::fs;
use std::io;

use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
B
Ben S 已提交
5
use column::{Column, Permissions, FileName, FileSize, User, Group};
B
Ben S 已提交
6
use format::{formatBinaryBytes, formatDecimalBytes};
B
Ben S 已提交
7
use unix::{get_user_name, get_group_name};
B
Ben S 已提交
8

9 10 11 12 13 14 15 16 17
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" ];

B
Ben S 已提交
18 19 20 21
// 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> {
B
Ben S 已提交
22
    pub name: &'a str,
B
Ben S 已提交
23
    pub ext:  Option<&'a str>,
B
Ben S 已提交
24 25
    pub path: &'a Path,
    pub stat: io::FileStat,
B
Ben S 已提交
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
}

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),
        };

B
Ben S 已提交
41 42 43 44 45 46
        return File {
            path: path,
            stat: stat,
            name: filename,
            ext:  File::ext(filename),
        };
B
Ben S 已提交
47 48
    }

B
Ben S 已提交
49
    fn ext(name: &'a str) -> Option<&'a str> {
B
Ben S 已提交
50
        let re = regex!(r"\.(.+)$");
B
Ben S 已提交
51
        re.captures(name).map(|caps| caps.at(1))
B
Ben S 已提交
52 53
    }

B
Ben S 已提交
54 55 56 57
    pub fn is_dotfile(&self) -> bool {
        self.name.starts_with(".")
    }

B
Ben S 已提交
58
    pub fn display(&self, column: &Column) -> StrBuf {
B
Ben S 已提交
59 60
        match *column {
            Permissions => self.permissions(),
B
Ben S 已提交
61
            FileName => self.file_colour().paint(self.name.as_slice()),
B
Ben S 已提交
62
            FileSize(si) => self.file_size(si),
63 64
            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()),
B
Ben S 已提交
65 66 67
        }
    }

B
Ben S 已提交
68
    fn file_size(&self, si: bool) -> StrBuf {
69 70 71 72
        // 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("---")
B
Ben S 已提交
73
        } else {
74 75 76 77 78
            let sizeStr = if si {
                formatBinaryBytes(self.stat.size)
            } else {
                formatDecimalBytes(self.stat.size)
            };
B
Ben S 已提交
79

80 81
            return Green.bold().paint(sizeStr.as_slice());
        }
B
Ben S 已提交
82 83
    }

B
Ben S 已提交
84
    fn type_char(&self) -> StrBuf {
B
Ben S 已提交
85
        return match self.stat.kind {
B
Ben S 已提交
86
            io::TypeFile => ".".to_strbuf(),
B
Ben S 已提交
87 88 89 90
            io::TypeDirectory => Blue.paint("d"),
            io::TypeNamedPipe => Yellow.paint("|"),
            io::TypeBlockSpecial => Purple.paint("s"),
            io::TypeSymlink => Cyan.paint("l"),
B
Ben S 已提交
91
            _ => "?".to_owned(),
B
Ben S 已提交
92 93 94 95 96 97
        }
    }

    fn file_colour(&self) -> Style {
        if self.stat.kind == io::TypeDirectory {
            Blue.normal()
98 99 100 101 102
        }
        else if self.stat.perm.contains(io::UserExecute) {
            Green.bold()
        }
        else if self.name.ends_with("~") {
B
Ben S 已提交
103
            Black.bold()
104 105 106 107 108 109 110 111 112 113 114
        }
        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 {
B
Ben S 已提交
115 116 117 118
            Plain
        }
    }

B
Ben S 已提交
119
    fn permissions(&self) -> StrBuf {
B
Ben S 已提交
120 121 122
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),
B
Ben S 已提交
123 124 125 126 127 128 129 130 131
            bit(bits, io::UserRead, "r", Yellow.bold()),
            bit(bits, io::UserWrite, "w", Red.bold()),
            bit(bits, io::UserExecute, "x", Green.bold().underline()),
            bit(bits, io::GroupRead, "r", Yellow.normal()),
            bit(bits, io::GroupWrite, "w", Red.normal()),
            bit(bits, io::GroupExecute, "x", Green.normal()),
            bit(bits, io::OtherRead, "r", Yellow.normal()),
            bit(bits, io::OtherWrite, "w", Red.normal()),
            bit(bits, io::OtherExecute, "x", Green.normal()),
B
Ben S 已提交
132 133 134 135
       );
    }
}

B
Ben S 已提交
136
fn bit(bits: io::FilePermission, bit: io::FilePermission, other: &'static str, style: Style) -> StrBuf {
B
Ben S 已提交
137
    if bits.contains(bit) {
B
Ben S 已提交
138
        style.paint(other.as_slice())
B
Ben S 已提交
139
    } else {
B
Ben S 已提交
140
        Black.bold().paint("-".as_slice())
B
Ben S 已提交
141 142
    }
}