mirror of
https://github.com/Llewellynvdm/exa.git
synced 2024-11-25 05:17:34 +00:00
Split source out into multiple files
Also, reverse the way columns are rendered: before, a column took a stat and a name to render; now, a file takes a column type to render. This means that most of the File data/methods can be private.
This commit is contained in:
parent
e0fc84e869
commit
10b8f6f414
@ -1,5 +1,3 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub enum Colour {
|
||||
Black = 30, Red = 31, Green = 32, Yellow = 33, Blue = 34, Purple = 35, Cyan = 36, White = 37,
|
||||
}
|
||||
|
13
column.rs
Normal file
13
column.rs
Normal file
@ -0,0 +1,13 @@
|
||||
pub enum Column {
|
||||
Permissions,
|
||||
FileName,
|
||||
FileSize(bool),
|
||||
}
|
||||
|
||||
pub fn defaultColumns() -> ~[Column] {
|
||||
return ~[
|
||||
Permissions,
|
||||
FileSize(false),
|
||||
FileName,
|
||||
];
|
||||
}
|
143
exa.rs
143
exa.rs
@ -1,10 +1,15 @@
|
||||
extern crate getopts;
|
||||
use std::io::fs;
|
||||
use std::io;
|
||||
use std::os;
|
||||
use std::io;
|
||||
use std::io::fs;
|
||||
|
||||
use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
|
||||
mod colours;
|
||||
use file::File;
|
||||
use column::{Column, defaultColumns};
|
||||
|
||||
pub mod colours;
|
||||
pub mod column;
|
||||
pub mod format;
|
||||
pub mod file;
|
||||
|
||||
struct Options {
|
||||
showInvisibles: bool,
|
||||
@ -36,97 +41,6 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
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(opts: Options, path: Path) {
|
||||
let mut files = match fs::readdir(&path) {
|
||||
Ok(files) => files,
|
||||
@ -140,13 +54,9 @@ fn list(opts: Options, path: Path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let columns = ~[
|
||||
~Permissions as ~Column,
|
||||
~FileSize { useSIPrefixes: false } as ~Column,
|
||||
~FileName as ~Column
|
||||
];
|
||||
let columns = defaultColumns();
|
||||
|
||||
let mut cells = columns.iter().map(|c| c.display(&file.stat, file.name));
|
||||
let mut cells = columns.iter().map(|c| file.display(c));
|
||||
|
||||
let mut first = true;
|
||||
for cell in cells {
|
||||
@ -160,34 +70,3 @@ fn list(opts: Options, path: Path) {
|
||||
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"),
|
||||
_ => ~"?",
|
||||
}
|
||||
}
|
||||
|
102
file.rs
Normal file
102
file.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::io::fs;
|
||||
use std::io;
|
||||
|
||||
use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
|
||||
use column::{Column, Permissions, FileName, FileSize};
|
||||
use format::{formatBinaryBytes, formatDecimalBytes};
|
||||
|
||||
// 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.
|
||||
pub struct File<'a> {
|
||||
name: &'a str,
|
||||
path: &'a Path,
|
||||
stat: io::FileStat,
|
||||
}
|
||||
|
||||
impl<'a> File<'a> {
|
||||
pub 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 };
|
||||
}
|
||||
|
||||
pub fn display(&self, column: &Column) -> ~str {
|
||||
match *column {
|
||||
Permissions => self.permissions(),
|
||||
FileName => self.file_colour().paint(self.name.to_owned()),
|
||||
FileSize(si) => self.file_size(si),
|
||||
}
|
||||
}
|
||||
|
||||
fn file_size(&self, si: bool) -> ~str {
|
||||
let sizeStr = if si {
|
||||
formatBinaryBytes(self.stat.size)
|
||||
} else {
|
||||
formatDecimalBytes(self.stat.size)
|
||||
};
|
||||
|
||||
return if self.stat.kind == io::TypeDirectory {
|
||||
Green.normal()
|
||||
} else {
|
||||
Green.bold()
|
||||
}.paint(sizeStr);
|
||||
}
|
||||
|
||||
fn type_char(&self) -> ~str {
|
||||
return match self.stat.kind {
|
||||
io::TypeFile => ~".",
|
||||
io::TypeDirectory => Blue.paint("d"),
|
||||
io::TypeNamedPipe => Yellow.paint("|"),
|
||||
io::TypeBlockSpecial => Purple.paint("s"),
|
||||
io::TypeSymlink => Cyan.paint("l"),
|
||||
_ => ~"?",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn file_colour(&self) -> Style {
|
||||
if self.stat.kind == io::TypeDirectory {
|
||||
Blue.normal()
|
||||
} else if self.stat.perm & io::UserExecute == io::UserExecute {
|
||||
Green.normal()
|
||||
} else if self.name.ends_with("~") {
|
||||
Black.bold()
|
||||
} else {
|
||||
Plain
|
||||
}
|
||||
}
|
||||
|
||||
fn permissions(&self) -> ~str {
|
||||
let bits = self.stat.perm;
|
||||
return format!("{}{}{}{}{}{}{}{}{}{}",
|
||||
self.type_char(),
|
||||
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 bit(bits: u32, bit: u32, other: ~str, style: Style) -> ~str {
|
||||
if bits & bit == bit {
|
||||
style.paint(other)
|
||||
} else {
|
||||
Black.bold().paint(~"-")
|
||||
}
|
||||
}
|
16
format.rs
Normal file
16
format.rs
Normal file
@ -0,0 +1,16 @@
|
||||
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]);
|
||||
}
|
||||
|
||||
pub fn formatBinaryBytes(amount: u64) -> ~str {
|
||||
formatBytes(amount, 1024, ~[ "B ", "KiB", "MiB", "GiB", "TiB" ])
|
||||
}
|
||||
|
||||
pub fn formatDecimalBytes(amount: u64) -> ~str {
|
||||
formatBytes(amount, 1000, ~[ "B ", "KB", "MB", "GB", "TB" ])
|
||||
}
|
Loading…
Reference in New Issue
Block a user