exa.rs 4.5 KB
Newer Older
B
Ben S 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
use std::io::fs;
use std::io;
use std::os;

use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
mod colours;

fn main() {
    match os::args().as_slice() {
        [] => unreachable!(),
        [_] => { list(Path::new(".")) },
        [_, ref p] => { list(Path::new(p.as_slice())) },
        _ => { fail!("args?") },
    }
}

B
Ben S 已提交
17 18 19 20 21 22 23 24
enum Permissions {
    Permissions,
}

enum FileName {
    FileName,
}

B
Ben S 已提交
25 26 27 28
struct FileSize {
    useSIPrefixes: bool,
}

B
Ben S 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
trait Column {
    fn display(&self, stat: &io::FileStat, filename: &str) -> ~str;
}

impl Column for FileName {
    fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
        file_colour(stat, filename).paint(filename.to_owned())
    }
}

impl Column for Permissions {
    fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
        let bits = stat.perm;
        return format!("{}{}{}{}{}{}{}{}{}{}",
            type_char(stat.kind),
            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 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
impl Column for FileSize {
    fn display(&self, stat: &io::FileStat, filename: &str) -> ~str {
        let sizeStr = if self.useSIPrefixes {
            formatBytes(stat.size, 1024, ~[ "B  ", "KiB", "MiB", "GiB", "TiB" ])
        } else {
            formatBytes(stat.size, 1000, ~[ "B ", "KB", "MB", "GB", "TB" ])
        };

        return if stat.kind == io::TypeDirectory {
            Green.normal()
        } else {
            Green.bold()
        }.paint(sizeStr);
    }
}

fn formatBytes(mut amount: u64, kilo: u64, prefixes: ~[&str]) -> ~str {
    let mut prefix = 0;
    while amount > kilo {
        amount /= kilo;
        prefix += 1;
    }
    return format!("{:4}{}", amount, prefixes[prefix]);
}

82 83 84 85 86 87 88 89 90 91 92 93
// 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.
struct File<'a> {
    name: &'a str,
    path: &'a Path,
    stat: io::FileStat,
}

impl<'a> File<'a> {
    fn from_path(path: &'a Path) -> File<'a> {
        let filename: &str = path.filename_str().unwrap();
B
Ben S 已提交
94 95 96 97 98

        // 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.
99
        let stat: io::FileStat = match fs::lstat(path) {
B
Ben S 已提交
100 101 102 103
            Ok(stat) => stat,
            Err(e) => fail!("Couldn't stat {}: {}", filename, e),
        };

104 105 106 107 108 109 110 111 112 113 114 115 116
        return File { path: path, stat: stat, name: filename };
    }
}

fn list(path: Path) {
    let mut files = match fs::readdir(&path) {
        Ok(files) => files,
        Err(e) => fail!("readdir: {}", e),
    };
    files.sort_by(|a, b| a.filename_str().cmp(&b.filename_str()));
    for subpath in files.iter() {
        let file = File::from_path(subpath);

B
Ben S 已提交
117 118 119 120 121 122
        let columns = ~[
            ~Permissions as ~Column,
            ~FileSize { useSIPrefixes: false } as ~Column,
            ~FileName as ~Column
        ];

123
        let mut cells = columns.iter().map(|c| c.display(&file.stat, file.name));
B
Ben S 已提交
124

B
Ben S 已提交
125 126 127 128 129 130 131 132 133 134
        let mut first = true;
        for cell in cells {
            if first {
                first = false;
            } else {
                print!(" ");
            }
            print!("{}", cell);
        }
        print!("\n");
B
Ben S 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    }
}

fn file_colour(stat: &io::FileStat, filename: &str) -> Style {
    if stat.kind == io::TypeDirectory {
        Blue.normal()
    } else if stat.perm & io::UserExecute == io::UserExecute {
        Green.normal()
    } else if filename.ends_with("~") {
        Black.bold()
    } else {
        Plain
    }
}

fn bit(bits: u32, bit: u32, other: ~str, style: Style) -> ~str {
    if bits & bit == bit {
        style.paint(other)
    } else {
        Black.bold().paint(~"-")
    }
}

fn type_char(t: io::FileType) -> ~str {
    return match t {
        io::TypeFile => ~".",
        io::TypeDirectory => Blue.paint("d"),
        io::TypeNamedPipe => Yellow.paint("|"),
        io::TypeBlockSpecial => Purple.paint("s"),
        io::TypeSymlink => Cyan.paint("l"),
        _ => ~"?",
    }
}