exa.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #![warn(trivial_casts, trivial_numeric_casts)]
  2. #![warn(unused_results)]
  3. extern crate ansi_term;
  4. extern crate datetime;
  5. extern crate glob;
  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 scoped_threadpool;
  12. extern crate term_grid;
  13. extern crate unicode_width;
  14. extern crate users;
  15. extern crate zoneinfo_compiled;
  16. extern crate term_size;
  17. #[cfg(feature="git")] extern crate git2;
  18. #[macro_use] extern crate lazy_static;
  19. #[macro_use] extern crate log;
  20. use std::env::var_os;
  21. use std::ffi::{OsStr, OsString};
  22. use std::io::{stderr, Write, Result as IOResult};
  23. use std::path::{Component, PathBuf};
  24. use ansi_term::{ANSIStrings, Style};
  25. use fs::{Dir, File};
  26. use fs::feature::ignore::IgnoreCache;
  27. use fs::feature::git::GitCache;
  28. use options::{Options, Vars};
  29. pub use options::Misfire;
  30. use output::{escape, lines, grid, grid_details, details, View, Mode};
  31. mod fs;
  32. mod info;
  33. mod options;
  34. mod output;
  35. mod style;
  36. /// The main program wrapper.
  37. pub struct Exa<'args, 'w, W: Write + 'w> {
  38. /// List of command-line options, having been successfully parsed.
  39. pub options: Options,
  40. /// The output handle that we write to. When running the program normally,
  41. /// this will be `std::io::Stdout`, but it can accept any struct that’s
  42. /// `Write` so we can write into, say, a vector for testing.
  43. pub writer: &'w mut W,
  44. /// List of the free command-line arguments that should correspond to file
  45. /// names (anything that isn’t an option).
  46. pub args: Vec<&'args OsStr>,
  47. /// A global Git cache, if the option was passed in.
  48. /// This has to last the lifetime of the program, because the user might
  49. /// want to list several directories in the same repository.
  50. pub git: Option<GitCache>,
  51. /// A cache of git-ignored files.
  52. /// This lasts the lifetime of the program too, for the same reason.
  53. pub ignore: Option<IgnoreCache>,
  54. }
  55. /// The “real” environment variables type.
  56. /// Instead of just calling `var_os` from within the options module,
  57. /// the method of looking up environment variables has to be passed in.
  58. struct LiveVars;
  59. impl Vars for LiveVars {
  60. fn get(&self, name: &'static str) -> Option<OsString> {
  61. var_os(name)
  62. }
  63. }
  64. /// Create a Git cache populated with the arguments that are going to be
  65. /// listed before they’re actually listed, if the options demand it.
  66. fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
  67. if options.should_scan_for_git() {
  68. Some(args.iter().map(|os| PathBuf::from(os)).collect())
  69. }
  70. else {
  71. None
  72. }
  73. }
  74. fn ignore_cache(options: &Options) -> Option<IgnoreCache> {
  75. use fs::filter::GitIgnore;
  76. match options.filter.git_ignore {
  77. GitIgnore::CheckAndIgnore => Some(IgnoreCache::new()),
  78. GitIgnore::Off => None,
  79. }
  80. }
  81. impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
  82. pub fn new<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
  83. where I: Iterator<Item=&'args OsString> {
  84. Options::parse(args, &LiveVars).map(move |(options, mut args)| {
  85. debug!("Dir action from arguments: {:#?}", options.dir_action);
  86. debug!("Filter from arguments: {:#?}", options.filter);
  87. debug!("View from arguments: {:#?}", options.view.mode);
  88. // List the current directory by default, like ls.
  89. // This has to be done here, otherwise git_options won’t see it.
  90. if args.is_empty() {
  91. args = vec![ OsStr::new(".") ];
  92. }
  93. let git = git_options(&options, &args);
  94. let ignore = ignore_cache(&options);
  95. Exa { options, writer, args, git, ignore }
  96. })
  97. }
  98. pub fn run(&mut self) -> IOResult<i32> {
  99. let mut files = Vec::new();
  100. let mut dirs = Vec::new();
  101. let mut exit_status = 0;
  102. for file_path in &self.args {
  103. match File::new(PathBuf::from(file_path), None, None) {
  104. Err(e) => {
  105. exit_status = 2;
  106. writeln!(stderr(), "{:?}: {}", file_path, e)?;
  107. },
  108. Ok(f) => {
  109. if f.is_directory() && !self.options.dir_action.treat_dirs_as_files() {
  110. match f.to_dir() {
  111. Ok(d) => dirs.push(d),
  112. Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
  113. }
  114. }
  115. else {
  116. files.push(f);
  117. }
  118. },
  119. }
  120. }
  121. // We want to print a directory’s name before we list it, *except* in
  122. // the case where it’s the only directory, *except* if there are any
  123. // files to print as well. (It’s a double negative)
  124. let no_files = files.is_empty();
  125. let is_only_dir = dirs.len() == 1 && no_files;
  126. self.options.filter.filter_argument_files(&mut files);
  127. self.print_files(None, files)?;
  128. self.print_dirs(dirs, no_files, is_only_dir, exit_status)
  129. }
  130. fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> IOResult<i32> {
  131. for dir in dir_files {
  132. // Put a gap between directories, or between the list of files and
  133. // the first directory.
  134. if first {
  135. first = false;
  136. }
  137. else {
  138. write!(self.writer, "\n")?;
  139. }
  140. if !is_only_dir {
  141. let mut bits = Vec::new();
  142. escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
  143. writeln!(self.writer, "{}:", ANSIStrings(&bits))?;
  144. }
  145. let mut children = Vec::new();
  146. for file in dir.files(self.options.filter.dot_filter, self.ignore.as_ref()) {
  147. match file {
  148. Ok(file) => children.push(file),
  149. Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?,
  150. }
  151. };
  152. self.options.filter.filter_child_files(&mut children);
  153. self.options.filter.sort_files(&mut children);
  154. if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
  155. let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
  156. if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
  157. let mut child_dirs = Vec::new();
  158. for child_dir in children.iter().filter(|f| f.is_directory()) {
  159. match child_dir.to_dir() {
  160. Ok(d) => child_dirs.push(d),
  161. Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
  162. }
  163. }
  164. self.print_files(Some(&dir), children)?;
  165. match self.print_dirs(child_dirs, false, false, exit_status) {
  166. Ok(_) => (),
  167. Err(e) => return Err(e),
  168. }
  169. continue;
  170. }
  171. }
  172. self.print_files(Some(&dir), children)?;
  173. }
  174. Ok(exit_status)
  175. }
  176. /// Prints the list of files using whichever view is selected.
  177. /// For various annoying logistical reasons, each one handles
  178. /// printing differently...
  179. fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
  180. if !files.is_empty() {
  181. let View { ref mode, ref colours, ref style } = self.options.view;
  182. match *mode {
  183. Mode::Lines => {
  184. lines::Render { files, colours, style }.render(self.writer)
  185. }
  186. Mode::Grid(ref opts) => {
  187. grid::Render { files, colours, style, opts }.render(self.writer)
  188. }
  189. Mode::Details(ref opts) => {
  190. details::Render { dir, files, colours, style, opts, filter: &self.options.filter, recurse: self.options.dir_action.recurse_options() }.render(self.git.as_ref(), self.ignore.as_ref(), self.writer)
  191. }
  192. Mode::GridDetails(ref opts) => {
  193. grid_details::Render { dir, files, colours, style, grid: &opts.grid, details: &opts.details, filter: &self.options.filter, row_threshold: opts.row_threshold }.render(self.git.as_ref(), self.writer)
  194. }
  195. }
  196. }
  197. else {
  198. Ok(())
  199. }
  200. }
  201. }