exa/exa.rs
Ben S af7689eb0b Keep files and their stat results together
Thanks to #rust for helping me diagnose lifetime problems!
2014-05-04 16:28:42 +01:00

168 lines
4.5 KiB
Rust

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?") },
}
}
enum Permissions {
Permissions,
}
enum FileName {
FileName,
}
struct FileSize {
useSIPrefixes: bool,
}
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()),
);
}
}
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]);
}
// 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();
// 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),
};
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);
let columns = ~[
~Permissions as ~Column,
~FileSize { useSIPrefixes: false } as ~Column,
~FileName as ~Column
];
let mut cells = columns.iter().map(|c| c.display(&file.stat, file.name));
let mut first = true;
for cell in cells {
if first {
first = false;
} else {
print!(" ");
}
print!("{}", cell);
}
print!("\n");
}
}
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"),
_ => ~"?",
}
}