diff --git a/README.md b/README.md index 0e55421..3da8340 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Options - **-1**, **--oneline**: display one entry per line - **-a**, **--all**: show dot files - **-b**, **--binary**: use binary (power of two) file sizes +- **-d**, **--list-dirs**: list directories as regular files - **-g**, **--group**: show group as well as user - **-h**, **--header**: show a header row - **-H**, **--links**: show number of hard links column @@ -31,4 +32,4 @@ You can sort by **name**, **size**, **ext**, **inode**, or **none**. Installation ------------ -exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. You will also need [Cargo](http://crates.io), the Rust package manager. Once you have them both set up, a simple `cargo build` will pull in all the dependencies and compile exa. +exa is written in [Rust](http://www.rust-lang.org). You'll have to use the nightly -- I try to keep it up to date with the latest version when possible. Once you have it set up, a simple `cargo build` will pull in all the dependencies and compile exa. diff --git a/src/exa.rs b/src/exa.rs index df82445..e14c5d2 100644 --- a/src/exa.rs +++ b/src/exa.rs @@ -36,24 +36,38 @@ fn main() { }; } -fn exa(opts: &Options) { +fn exa(opts: &Options) { + let mut dirs: Vec = vec![]; + let mut files: Vec = vec![]; + + // Separate the user-supplied paths into directories and files. + // Files are shown first, and then each directory is expanded + // and listed second. + for file in opts.path_strs.iter() { + let path = Path::new(file); + match fs::stat(&path) { + Ok(stat) => { + if !opts.list_dirs && stat.kind == TypeDirectory { + dirs.push(file.clone()); + } + else { + // May as well reuse the stat result from earlier + // instead of just using File::from_path(). + files.push(File::with_stat(stat, path, None)); + } + } + Err(e) => println!("{}: {}", file, e), + } + } + // It's only worth printing out directory names if the user supplied // more than one of them. let print_dir_names = opts.path_strs.len() > 1; - let (dir_strs, file_strs) = opts.path_strs.clone().partition(|n| fs::stat(&Path::new(n)).unwrap().kind == TypeDirectory); - - let mut first = file_strs.is_empty(); - let mut files = vec![]; - for f in file_strs.iter() { - match File::from_path(Path::new(f), None) { - Ok(file) => files.push(file), - Err(e) => println!("{}: {}", f, e), - } - } - + let mut first = files.is_empty(); + view(opts, files); - for dir_name in dir_strs.into_iter() { + for dir_name in dirs.into_iter() { if first { first = false; } diff --git a/src/file.rs b/src/file.rs index 4343e18..17705cc 100644 --- a/src/file.rs +++ b/src/file.rs @@ -32,21 +32,24 @@ pub struct File<'a> { impl<'a> File<'a> { pub fn from_path(path: Path, parent: Option<&'a Dir<'a>>) -> IoResult> { - let v = path.filename().unwrap(); // fails if / or . or .. - let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8")); - // Use lstat here instead 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. + fs::lstat(&path).map(|stat| File::with_stat(stat, path.clone(), parent)) + } + + pub fn with_stat(stat: io::FileStat, path: Path, parent: Option<&'a Dir<'a>>) -> File<'a> { + let v = path.filename().unwrap(); // fails if / or . or .. + let filename = String::from_utf8(v.to_vec()).unwrap_or_else(|_| panic!("Name was not valid UTF-8")); - fs::lstat(&path).map(|stat| File { + File { path: path.clone(), dir: parent, stat: stat, name: filename.clone(), ext: File::ext(filename.clone()), parts: SortPart::split_into_parts(filename.clone()), - }) + } } fn ext(name: String) -> Option { diff --git a/src/options.rs b/src/options.rs index 39882e6..4cf8081 100644 --- a/src/options.rs +++ b/src/options.rs @@ -32,6 +32,7 @@ pub enum View { pub struct Options { pub header: bool, + pub list_dirs: bool, pub path_strs: Vec, pub reverse: bool, pub show_invisibles: bool, @@ -43,24 +44,26 @@ pub struct Options { impl Options { pub fn getopts(args: Vec) -> Result { let opts = &[ - getopts::optflag("1", "oneline", "display one entry per line"), - getopts::optflag("a", "all", "show dot-files"), - getopts::optflag("b", "binary", "use binary prefixes in file sizes"), - getopts::optflag("g", "group", "show group as well as user"), - getopts::optflag("h", "header", "show a header row at the top"), - getopts::optflag("H", "links", "show number of hard links"), - getopts::optflag("l", "long", "display extended details and attributes"), - getopts::optflag("i", "inode", "show each file's inode number"), - getopts::optflag("r", "reverse", "reverse order of files"), - getopts::optopt ("s", "sort", "field to sort by", "WORD"), - getopts::optflag("S", "blocks", "show number of file system blocks"), - getopts::optflag("x", "across", "sort multi-column view entries across"), + getopts::optflag("1", "oneline", "display one entry per line"), + getopts::optflag("a", "all", "show dot-files"), + getopts::optflag("b", "binary", "use binary prefixes in file sizes"), + getopts::optflag("d", "list-dirs", "list directories as regular files"), + getopts::optflag("g", "group", "show group as well as user"), + getopts::optflag("h", "header", "show a header row at the top"), + getopts::optflag("H", "links", "show number of hard links"), + getopts::optflag("l", "long", "display extended details and attributes"), + getopts::optflag("i", "inode", "show each file's inode number"), + getopts::optflag("r", "reverse", "reverse order of files"), + getopts::optopt ("s", "sort", "field to sort by", "WORD"), + getopts::optflag("S", "blocks", "show number of file system blocks"), + getopts::optflag("x", "across", "sort multi-column view entries across"), ]; match getopts::getopts(args.tail(), opts) { Err(f) => Err(f), Ok(ref matches) => Ok(Options { header: matches.opt_present("header"), + list_dirs: matches.opt_present("list-dirs"), path_strs: if matches.free.is_empty() { vec![ ".".to_string() ] } else { matches.free.clone() }, reverse: matches.opt_present("reverse"), show_invisibles: matches.opt_present("all"),