mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-14 00:14:05 +00:00
117 lines
3.3 KiB
Rust
117 lines
3.3 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,
|
|
}
|
|
|
|
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()),
|
|
);
|
|
}
|
|
}
|
|
|
|
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, ~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"),
|
|
_ => ~"?",
|
|
}
|
|
}
|