From 5611a5768a832c0373a7d4da95175b651565f80a Mon Sep 17 00:00:00 2001 From: Ben S Date: Sun, 1 Feb 2015 02:14:31 +0000 Subject: [PATCH] Recurse into directories --- src/dir.rs | 6 +++--- src/main.rs | 57 ++++++++++++++++++++++++++++---------------------- src/options.rs | 2 +- src/output.rs | 12 +++++------ 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/dir.rs b/src/dir.rs index f9f8437..d938cbb 100644 --- a/src/dir.rs +++ b/src/dir.rs @@ -21,11 +21,11 @@ impl Dir { /// Create a new Dir object filled with all the files in the directory /// pointed to by the given path. Fails if the directory can't be read, or /// isn't actually a directory. - pub fn readdir(path: Path) -> IoResult { - fs::readdir(&path).map(|paths| Dir { + pub fn readdir(path: &Path) -> IoResult { + fs::readdir(path).map(|paths| Dir { contents: paths, path: path.clone(), - git: Git::scan(&path).ok(), + git: Git::scan(path).ok(), }) } diff --git a/src/main.rs b/src/main.rs index 5bab5ac..c31c6cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,37 +25,24 @@ pub mod output; pub mod term; fn exa(options: &Options) { - let mut dirs: Vec = vec![]; + let mut dirs: Vec = vec![]; let mut files: Vec = vec![]; // It's only worth printing out directory names if the user supplied // more than one of them. let mut count = 0; - let mut stack = options.path_strs.clone(); - // Separate the user-supplied paths into directories and files. // Files are shown first, and then each directory is expanded // and listed second. - loop { - let file = match stack.pop() { - None => break, - Some(f) => f, - }; - - let path = Path::new(file.clone()); + for file in options.path_strs.iter() { + let path = Path::new(file); match fs::stat(&path) { Ok(stat) => { - if stat.kind == FileType::Directory { - match options.dir_action { - DirAction::AsFile => files.push(File::with_stat(stat, &path, None)), - DirAction::List => dirs.push(file.clone()), - DirAction::Recurse => { /* todo */ }, - } + if stat.kind == FileType::Directory && options.dir_action != DirAction::AsFile { + dirs.push(path); } 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)); } } @@ -68,10 +55,19 @@ fn exa(options: &Options) { let mut first = files.is_empty(); if !files.is_empty() { - options.view(None, files); + options.view(None, &files[]); } - for dir_name in dirs.iter() { + // Directories are put on a stack rather than just being iterated through, + // as the vector can change as more directories are added. + loop { + let dir_path = match dirs.pop() { + None => break, + Some(f) => f, + }; + + // Put a gap between directories, or between the list of files and the + // first directory. if first { first = false; } @@ -79,19 +75,30 @@ fn exa(options: &Options) { print!("\n"); } - match Dir::readdir(Path::new(dir_name.clone())) { + match Dir::readdir(&dir_path) { Ok(ref dir) => { let unsorted_files = dir.files(); let files: Vec = options.transform_files(unsorted_files); - if count > 1 { - println!("{}:", dir_name); + // When recursing, add any directories to the dirs stack + // backwards: the *last* element of the stack is used each + // time, so by inserting them backwards, they get displayed in + // the correct sort order. + if options.dir_action == DirAction::Recurse { + for dir in files.iter().filter(|f| f.stat.kind == FileType::Directory).rev() { + dirs.push(dir.path.clone()); + } } - options.view(Some(dir), files); + if count > 1 { + println!("{}:", dir_path.display()); + } + count += 1; + + options.view(Some(dir), &files[]); } Err(e) => { - println!("{}: {}", dir_name, e); + println!("{}: {}", dir_path.display(), e); return; } }; diff --git a/src/options.rs b/src/options.rs index 219b67d..8d1f355 100644 --- a/src/options.rs +++ b/src/options.rs @@ -74,7 +74,7 @@ impl Options { } /// Display the files using this Option's View. - pub fn view(&self, dir: Option<&Dir>, files: Vec) { + pub fn view(&self, dir: Option<&Dir>, files: &[File]) { self.view.view(dir, files) } diff --git a/src/output.rs b/src/output.rs index 7b8bd39..52251d4 100644 --- a/src/output.rs +++ b/src/output.rs @@ -18,7 +18,7 @@ pub enum View { } impl View { - pub fn view(&self, dir: Option<&Dir>, files: Vec) { + pub fn view(&self, dir: Option<&Dir>, files: &[File]) { match *self { View::Grid(across, width) => grid_view(across, width, files), View::Details(ref cols, header) => details_view(&*cols.for_dir(dir), files, header), @@ -28,13 +28,13 @@ impl View { } /// The lines view literally just displays each file, line-by-line. -fn lines_view(files: Vec) { +fn lines_view(files: &[File]) { for file in files.iter() { println!("{}", file.file_name_view().text); } } -fn fit_into_grid(across: bool, console_width: usize, files: &Vec) -> Option<(usize, Vec)> { +fn fit_into_grid(across: bool, console_width: usize, files: &[File]) -> Option<(usize, Vec)> { // TODO: this function could almost certainly be optimised... // surely not *all* of the numbers of lines are worth searching through! @@ -86,8 +86,8 @@ fn fit_into_grid(across: bool, console_width: usize, files: &Vec) -> Optio return None; } -fn grid_view(across: bool, console_width: usize, files: Vec) { - if let Some((num_lines, widths)) = fit_into_grid(across, console_width, &files) { +fn grid_view(across: bool, console_width: usize, files: &[File]) { + if let Some((num_lines, widths)) = fit_into_grid(across, console_width, files) { for y in range(0, num_lines) { for x in range(0, widths.len()) { let num = if across { @@ -122,7 +122,7 @@ fn grid_view(across: bool, console_width: usize, files: Vec) { } } -fn details_view(columns: &[Column], files: Vec, header: bool) { +fn details_view(columns: &[Column], files: &[File], header: bool) { // The output gets formatted into columns, which looks nicer. To // do this, we have to write the results into a table, instead of // displaying each file immediately, then calculating the maximum