|
@@ -1,10 +1,15 @@
|
|
|
extern crate getopts;
|
|
extern crate getopts;
|
|
|
-use std::io::fs;
|
|
|
|
|
-use std::io;
|
|
|
|
|
use std::os;
|
|
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 {
|
|
struct Options {
|
|
|
showInvisibles: bool,
|
|
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) {
|
|
fn list(opts: Options, path: Path) {
|
|
|
let mut files = match fs::readdir(&path) {
|
|
let mut files = match fs::readdir(&path) {
|
|
|
Ok(files) => files,
|
|
Ok(files) => files,
|
|
@@ -140,13 +54,9 @@ fn list(opts: Options, path: Path) {
|
|
|
continue;
|
|
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;
|
|
let mut first = true;
|
|
|
for cell in cells {
|
|
for cell in cells {
|
|
@@ -160,34 +70,3 @@ fn list(opts: Options, path: Path) {
|
|
|
print!("\n");
|
|
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"),
|
|
|
|
|
- _ => ~"?",
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|