main.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #![feature(iter_arith)]
  2. #![feature(convert, fs_mode)]
  3. #![feature(slice_splits, vec_resize)]
  4. extern crate ansi_term;
  5. extern crate datetime;
  6. extern crate getopts;
  7. extern crate libc;
  8. extern crate locale;
  9. extern crate natord;
  10. extern crate num_cpus;
  11. extern crate number_prefix;
  12. extern crate pad;
  13. extern crate term_grid;
  14. extern crate threadpool;
  15. extern crate unicode_width;
  16. extern crate users;
  17. #[cfg(feature="git")]
  18. extern crate git2;
  19. use std::env;
  20. use std::fs;
  21. use std::path::{Component, Path, PathBuf};
  22. use std::process;
  23. use std::sync::mpsc::channel;
  24. use threadpool::ThreadPool;
  25. use dir::Dir;
  26. use file::File;
  27. use options::{Options, View};
  28. mod colours;
  29. mod column;
  30. mod dir;
  31. mod feature;
  32. mod file;
  33. mod filetype;
  34. mod options;
  35. mod output;
  36. mod term;
  37. #[cfg(not(test))]
  38. struct Exa<'dir> {
  39. count: usize,
  40. options: Options,
  41. dirs: Vec<PathBuf>,
  42. files: Vec<File<'dir>>,
  43. }
  44. #[cfg(not(test))]
  45. impl<'dir> Exa<'dir> {
  46. fn new(options: Options) -> Exa<'dir> {
  47. Exa {
  48. count: 0,
  49. options: options,
  50. dirs: Vec::new(),
  51. files: Vec::new(),
  52. }
  53. }
  54. fn load(&mut self, files: &[String]) {
  55. // Separate the user-supplied paths into directories and files.
  56. // Files are shown first, and then each directory is expanded
  57. // and listed second.
  58. let is_tree = self.options.dir_action.is_tree() || self.options.dir_action.is_as_file();
  59. let total_files = files.len();
  60. // Communication between consumer thread and producer threads
  61. enum StatResult<'dir> {
  62. File(File<'dir>),
  63. Dir(PathBuf),
  64. Error
  65. }
  66. let pool = ThreadPool::new(8 * num_cpus::get());
  67. let (tx, rx) = channel();
  68. for file in files.iter() {
  69. let tx = tx.clone();
  70. let file = file.clone();
  71. // Spawn producer thread
  72. pool.execute(move || {
  73. let path = Path::new(&*file);
  74. let _ = tx.send(match fs::metadata(&path) {
  75. Ok(metadata) => {
  76. if is_tree || !metadata.is_dir() {
  77. StatResult::File(File::with_metadata(metadata, &path, None))
  78. }
  79. else {
  80. StatResult::Dir(path.to_path_buf())
  81. }
  82. }
  83. Err(e) => {
  84. println!("{}: {}", file, e);
  85. StatResult::Error
  86. }
  87. });
  88. });
  89. }
  90. // Spawn consumer thread
  91. for result in rx.iter().take(total_files) {
  92. match result {
  93. StatResult::File(file) => self.files.push(file),
  94. StatResult::Dir(path) => self.dirs.push(path),
  95. StatResult::Error => ()
  96. }
  97. self.count += 1;
  98. }
  99. }
  100. fn print_files(&self) {
  101. if !self.files.is_empty() {
  102. self.print(None, &self.files[..]);
  103. }
  104. }
  105. fn print_dirs(&mut self) {
  106. let mut first = self.files.is_empty();
  107. // Directories are put on a stack rather than just being iterated through,
  108. // as the vector can change as more directories are added.
  109. loop {
  110. let dir_path = match self.dirs.pop() {
  111. None => break,
  112. Some(f) => f,
  113. };
  114. // Put a gap between directories, or between the list of files and the
  115. // first directory.
  116. if first {
  117. first = false;
  118. }
  119. else {
  120. print!("\n");
  121. }
  122. match Dir::readdir(&dir_path, self.options.should_scan_for_git()) {
  123. Ok(ref dir) => {
  124. let mut files = Vec::new();
  125. for file in dir.files() {
  126. match file {
  127. Ok(file) => files.push(file),
  128. Err((path, e)) => println!("[{}: {}]", path.display(), e),
  129. }
  130. }
  131. self.options.transform_files(&mut files);
  132. // When recursing, add any directories to the dirs stack
  133. // backwards: the *last* element of the stack is used each
  134. // time, so by inserting them backwards, they get displayed in
  135. // the correct sort order.
  136. if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
  137. let depth = dir_path.components().filter(|&c| c != Component::CurDir).count() + 1;
  138. if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
  139. for dir in files.iter().filter(|f| f.is_directory()).rev() {
  140. self.dirs.push(dir.path.clone());
  141. }
  142. }
  143. }
  144. if self.count > 1 {
  145. println!("{}:", dir_path.display());
  146. }
  147. self.count += 1;
  148. self.print(Some(dir), &files[..]);
  149. }
  150. Err(e) => {
  151. println!("{}: {}", dir_path.display(), e);
  152. return;
  153. }
  154. };
  155. }
  156. }
  157. fn print(&self, dir: Option<&Dir>, files: &[File]) {
  158. match self.options.view {
  159. View::Grid(g) => g.view(files),
  160. View::Details(d) => d.view(dir, files),
  161. View::GridDetails(gd) => gd.view(dir, files),
  162. View::Lines(l) => l.view(files),
  163. }
  164. }
  165. }
  166. #[cfg(not(test))]
  167. fn main() {
  168. let args: Vec<String> = env::args().skip(1).collect();
  169. match Options::getopts(&args) {
  170. Ok((options, paths)) => {
  171. let mut exa = Exa::new(options);
  172. exa.load(&paths);
  173. exa.print_files();
  174. exa.print_dirs();
  175. },
  176. Err(e) => {
  177. println!("{}", e);
  178. process::exit(e.error_code());
  179. },
  180. };
  181. }