file.rs 5.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::{format_metric_bytes, format_IEC_bytes};
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 22 23 24
// Instead of working with Rust's Paths, we have our own File object
// that holds the Path and various cached information. Each file is
// definitely going to have its filename used at least once, its stat
// information queried at least once, and its file extension extracted
// at least once, so we may as well carry around that information with
// the actual path.

B
Ben S 已提交
25
pub struct File<'a> {
B
Ben S 已提交
26
    pub name: &'a str,
B
Ben S 已提交
27
    pub ext:  Option<&'a str>,
B
Ben S 已提交
28 29
    pub path: &'a Path,
    pub stat: io::FileStat,
B
Ben S 已提交
30 31 32 33
}

impl<'a> File<'a> {
    pub fn from_path(path: &'a Path) -> File<'a> {
B
Ben S 已提交
34 35
        // Getting the string from a filename fails whenever it's not
        // UTF-8 representable - just assume it is for now.
B
Ben S 已提交
36 37
        let filename: &str = path.filename_str().unwrap();

B
Ben S 已提交
38 39 40
        // Use lstat here instead 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.
B
Ben S 已提交
41 42 43 44 45
        let stat: io::FileStat = match fs::lstat(path) {
            Ok(stat) => stat,
            Err(e) => fail!("Couldn't stat {}: {}", filename, e),
        };

B
Ben S 已提交
46 47 48 49 50 51
        return File {
            path: path,
            stat: stat,
            name: filename,
            ext:  File::ext(filename),
        };
B
Ben S 已提交
52 53
    }

B
Ben S 已提交
54
    fn ext(name: &'a str) -> Option<&'a str> {
B
Ben S 已提交
55 56 57 58
        // The extension is the series of characters after a dot at
        // the end of a filename. This deliberately also counts
        // dotfiles - the ".git" folder has the extension "git".
        let re = regex!(r"\.([^.]+)$");
B
Ben S 已提交
59
        re.captures(name).map(|caps| caps.at(1))
B
Ben S 已提交
60 61
    }

B
Ben S 已提交
62 63 64 65
    pub fn is_dotfile(&self) -> bool {
        self.name.starts_with(".")
    }

B
Ben S 已提交
66
    pub fn display(&self, column: &Column) -> String {
B
Ben S 已提交
67
        match *column {
B
Ben S 已提交
68
            Permissions => self.permissions_string(),
B
Ben S 已提交
69
            FileName => self.file_colour().paint(self.name.as_slice()),
B
Ben S 已提交
70 71 72 73
            FileSize(use_iec) => self.file_size(use_iec),

            // Display the ID if the user/group doesn't exist, which
            // usually means it was deleted but its files weren't.
74 75
            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 已提交
76 77 78
        }
    }

B
Ben S 已提交
79
    fn file_size(&self, use_iec_prefixes: bool) -> String {
80 81 82 83
        // 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 已提交
84
        } else {
B
Ben S 已提交
85 86
            let size_str = if use_iec_prefixes {
                format_IEC_bytes(self.stat.size)
87
            } else {
B
Ben S 已提交
88
                format_metric_bytes(self.stat.size)
89
            };
B
Ben S 已提交
90

B
Ben S 已提交
91
            return Green.bold().paint(size_str.as_slice());
92
        }
B
Ben S 已提交
93 94
    }

B
Ben S 已提交
95
    fn type_char(&self) -> String {
B
Ben S 已提交
96
        return match self.stat.kind {
B
Ben S 已提交
97 98 99
            io::TypeFile         => ".".to_strbuf(),
            io::TypeDirectory    => Blue.paint("d"),
            io::TypeNamedPipe    => Yellow.paint("|"),
B
Ben S 已提交
100
            io::TypeBlockSpecial => Purple.paint("s"),
B
Ben S 已提交
101 102
            io::TypeSymlink      => Cyan.paint("l"),
            _                    => "?".to_owned(),
B
Ben S 已提交
103 104 105 106 107 108
        }
    }

    fn file_colour(&self) -> Style {
        if self.stat.kind == io::TypeDirectory {
            Blue.normal()
109 110 111 112 113
        }
        else if self.stat.perm.contains(io::UserExecute) {
            Green.bold()
        }
        else if self.name.ends_with("~") {
B
Ben S 已提交
114
            Black.bold()
115 116 117 118 119 120 121 122 123 124 125
        }
        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 已提交
126 127 128 129
            Plain
        }
    }

B
Ben S 已提交
130
    fn permissions_string(&self) -> String {
B
Ben S 已提交
131 132 133
        let bits = self.stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            self.type_char(),
B
Ben S 已提交
134 135 136 137 138 139 140 141 142 143 144 145

            // The first three are bold because they're the ones used
            // most often.
            File::permission_bit(bits, io::UserRead,     "r", Yellow.bold()),
            File::permission_bit(bits, io::UserWrite,    "w", Red.bold()),
            File::permission_bit(bits, io::UserExecute,  "x", Green.bold().underline()),
            File::permission_bit(bits, io::GroupRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::GroupWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::GroupExecute, "x", Green.normal()),
            File::permission_bit(bits, io::OtherRead,    "r", Yellow.normal()),
            File::permission_bit(bits, io::OtherWrite,   "w", Red.normal()),
            File::permission_bit(bits, io::OtherExecute, "x", Green.normal()),
B
Ben S 已提交
146 147
       );
    }
148

B
Ben S 已提交
149
    fn permission_bit(bits: io::FilePermission, bit: io::FilePermission, character: &'static str, style: Style) -> String {
150
        if bits.contains(bit) {
B
Ben S 已提交
151
            style.paint(character.as_slice())
152 153 154 155
        } else {
            Black.bold().paint("-".as_slice())
        }
    }
B
Ben S 已提交
156
}