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:
Ben S 2014-05-04 21:33:14 +01:00
parent e0fc84e869
commit 10b8f6f414
5 changed files with 142 additions and 134 deletions

View File

@ -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
View File

@ -0,0 +1,13 @@
pub enum Column {
Permissions,
FileName,
FileSize(bool),
}
pub fn defaultColumns() -> ~[Column] {
return ~[
Permissions,
FileSize(false),
FileName,
];
}

143
exa.rs
View File

@ -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
View 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
View 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" ])
}