exa/exa.rs
2014-05-03 14:26:49 +01:00

151 lines
4.1 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]);
}
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 file in files.iter() {
let filename: &str = file.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(file) {
Ok(stat) => stat,
Err(e) => fail!("Couldn't stat {}: {}", filename, e),
};
let columns = ~[
~Permissions as ~Column,
~FileSize { useSIPrefixes: false } as ~Column,
~FileName as ~Column
];
let mut cells = columns.iter().map(|c| c.display(&stat, filename));
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"),
_ => ~"?",
}
}