diff --git a/colours.rs b/colours.rs index bf8928743385a3d43070053f6f0edd9cfb2e4cec..5ecb3b4410f2337f5173b90f253926de5eeb9399 100644 --- a/colours.rs +++ b/colours.rs @@ -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, } diff --git a/column.rs b/column.rs new file mode 100644 index 0000000000000000000000000000000000000000..963c2dac5c3f118e8019dd965a8c9b9612586b76 --- /dev/null +++ b/column.rs @@ -0,0 +1,13 @@ +pub enum Column { + Permissions, + FileName, + FileSize(bool), +} + +pub fn defaultColumns() -> ~[Column] { + return ~[ + Permissions, + FileSize(false), + FileName, + ]; +} diff --git a/exa.rs b/exa.rs index ea56ede1055b06c2461848becf34905d5571b5a0..ba125cc156d4a4300c6874c10a6d6d8d03561937 100644 --- a/exa.rs +++ b/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 file::File; +use column::{Column, defaultColumns}; -use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan}; -mod colours; +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"), - _ => ~"?", - } -} diff --git a/file.rs b/file.rs new file mode 100644 index 0000000000000000000000000000000000000000..92b2e0dc12b279051a05c6e7f272e48c5020005e --- /dev/null +++ b/file.rs @@ -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(~"-") + } +} diff --git a/format.rs b/format.rs new file mode 100644 index 0000000000000000000000000000000000000000..6099bcf4266d8aa3d54d7bfc6cadd8f09ee5b67a --- /dev/null +++ b/format.rs @@ -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" ]) +}