Add a flag to print directories as files

Also, re-use the stat result from directory-checking.
This commit is contained in:
Ben S 2014-11-25 01:27:26 +00:00
parent cbd2f1fa37
commit bcaf54d7dd
4 changed files with 52 additions and 31 deletions

View File

@ -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.

View File

@ -36,24 +36,38 @@ fn main() {
};
}
fn exa(opts: &Options) {
fn exa(opts: &Options) {
let mut dirs: Vec<String> = vec![];
let mut files: Vec<File> = 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;
}

View File

@ -32,21 +32,24 @@ pub struct File<'a> {
impl<'a> File<'a> {
pub fn from_path(path: Path, parent: Option<&'a Dir<'a>>) -> IoResult<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"));
// 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<String> {

View File

@ -32,6 +32,7 @@ pub enum View {
pub struct Options {
pub header: bool,
pub list_dirs: bool,
pub path_strs: Vec<String>,
pub reverse: bool,
pub show_invisibles: bool,
@ -43,24 +44,26 @@ pub struct Options {
impl Options {
pub fn getopts(args: Vec<String>) -> Result<Options, getopts::Fail_> {
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"),