main.rs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #![feature(collections, convert, core, exit_status, file_type, fs_ext, fs_mode)]
  2. #![feature(metadata_ext, raw_ext, symlink_metadata)]
  3. extern crate ansi_term;
  4. extern crate datetime;
  5. extern crate getopts;
  6. extern crate libc;
  7. extern crate locale;
  8. extern crate natord;
  9. extern crate num_cpus;
  10. extern crate number_prefix;
  11. extern crate pad;
  12. extern crate threadpool;
  13. extern crate unicode_width;
  14. extern crate users;
  15. #[cfg(feature="git")]
  16. extern crate git2;
  17. use std::env;
  18. use std::fs;
  19. use std::path::{Component, Path, PathBuf};
  20. use std::sync::mpsc::channel;
  21. use threadpool::ThreadPool;
  22. use dir::Dir;
  23. use file::File;
  24. use options::{Options, View};
  25. mod colours;
  26. mod column;
  27. mod dir;
  28. mod feature;
  29. mod file;
  30. mod filetype;
  31. mod options;
  32. mod output;
  33. mod term;
  34. #[cfg(not(test))]
  35. struct Exa<'dir> {
  36. count: usize,
  37. options: Options,
  38. dirs: Vec<PathBuf>,
  39. files: Vec<File<'dir>>,
  40. }
  41. #[cfg(not(test))]
  42. impl<'dir> Exa<'dir> {
  43. fn new(options: Options) -> Exa<'dir> {
  44. Exa {
  45. count: 0,
  46. options: options,
  47. dirs: Vec::new(),
  48. files: Vec::new(),
  49. }
  50. }
  51. fn load(&mut self, files: &[String]) {
  52. // Separate the user-supplied paths into directories and files.
  53. // Files are shown first, and then each directory is expanded
  54. // and listed second.
  55. let is_tree = self.options.dir_action.is_tree() || self.options.dir_action.is_as_file();
  56. let total_files = files.len();
  57. // Communication between consumer thread and producer threads
  58. enum StatResult<'dir> {
  59. File(File<'dir>),
  60. Dir(PathBuf),
  61. Error
  62. }
  63. let pool = ThreadPool::new(8 * num_cpus::get());
  64. let (tx, rx) = channel();
  65. for file in files.iter() {
  66. let tx = tx.clone();
  67. let file = file.clone();
  68. // Spawn producer thread
  69. pool.execute(move || {
  70. let path = Path::new(&*file);
  71. let _ = tx.send(match fs::metadata(&path) {
  72. Ok(metadata) => {
  73. if !metadata.is_dir() {
  74. StatResult::File(File::with_metadata(metadata, &path, None, false))
  75. }
  76. else if is_tree {
  77. StatResult::File(File::with_metadata(metadata, &path, None, true))
  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) {
  123. Ok(ref dir) => {
  124. let mut files = dir.files(false);
  125. self.options.transform_files(&mut files);
  126. // When recursing, add any directories to the dirs stack
  127. // backwards: the *last* element of the stack is used each
  128. // time, so by inserting them backwards, they get displayed in
  129. // the correct sort order.
  130. if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
  131. let depth = dir_path.components().filter(|&c| c != Component::CurDir).count() + 1;
  132. if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
  133. for dir in files.iter().filter(|f| f.is_directory()).rev() {
  134. self.dirs.push(dir.path.clone());
  135. }
  136. }
  137. }
  138. if self.count > 1 {
  139. println!("{}:", dir_path.display());
  140. }
  141. self.count += 1;
  142. self.print(Some(dir), &files[..]);
  143. }
  144. Err(e) => {
  145. println!("{}: {}", dir_path.display(), e);
  146. return;
  147. }
  148. };
  149. }
  150. }
  151. fn print(&self, dir: Option<&Dir>, files: &[File]) {
  152. match self.options.view {
  153. View::Grid(g) => g.view(files),
  154. View::Details(d) => d.view(dir, files),
  155. View::Lines(l) => l.view(files),
  156. }
  157. }
  158. }
  159. #[cfg(not(test))]
  160. fn main() {
  161. let args: Vec<String> = env::args().collect();
  162. match Options::getopts(args.tail()) {
  163. Ok((options, paths)) => {
  164. let mut exa = Exa::new(options);
  165. exa.load(&paths);
  166. exa.print_files();
  167. exa.print_dirs();
  168. },
  169. Err(e) => {
  170. println!("{}", e);
  171. env::set_exit_status(e.error_code());
  172. },
  173. };
  174. }