| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- #![warn(trivial_casts, trivial_numeric_casts)]
- #![warn(unused_results)]
- extern crate ansi_term;
- extern crate datetime;
- extern crate glob;
- extern crate libc;
- extern crate locale;
- extern crate natord;
- extern crate num_cpus;
- extern crate number_prefix;
- extern crate scoped_threadpool;
- extern crate term_grid;
- extern crate unicode_width;
- extern crate users;
- extern crate zoneinfo_compiled;
- extern crate term_size;
- #[cfg(feature="git")] extern crate git2;
- #[macro_use] extern crate lazy_static;
- #[macro_use] extern crate log;
- use std::env::var_os;
- use std::ffi::{OsStr, OsString};
- use std::io::{stderr, Write, Result as IOResult};
- use std::path::{Component, PathBuf};
- use ansi_term::{ANSIStrings, Style};
- use fs::{Dir, File};
- use fs::feature::ignore::IgnoreCache;
- use fs::feature::git::GitCache;
- use options::{Options, Vars};
- pub use options::vars;
- pub use options::Misfire;
- use output::{escape, lines, grid, grid_details, details, View, Mode};
- mod fs;
- mod info;
- mod options;
- mod output;
- mod style;
- /// The main program wrapper.
- pub struct Exa<'args, 'w, W: Write + 'w> {
- /// List of command-line options, having been successfully parsed.
- pub options: Options,
- /// The output handle that we write to. When running the program normally,
- /// this will be `std::io::Stdout`, but it can accept any struct that’s
- /// `Write` so we can write into, say, a vector for testing.
- pub writer: &'w mut W,
- /// List of the free command-line arguments that should correspond to file
- /// names (anything that isn’t an option).
- pub args: Vec<&'args OsStr>,
- /// A global Git cache, if the option was passed in.
- /// This has to last the lifetime of the program, because the user might
- /// want to list several directories in the same repository.
- pub git: Option<GitCache>,
- /// A cache of git-ignored files.
- /// This lasts the lifetime of the program too, for the same reason.
- pub ignore: Option<IgnoreCache>,
- }
- /// The “real” environment variables type.
- /// Instead of just calling `var_os` from within the options module,
- /// the method of looking up environment variables has to be passed in.
- struct LiveVars;
- impl Vars for LiveVars {
- fn get(&self, name: &'static str) -> Option<OsString> {
- var_os(name)
- }
- }
- /// Create a Git cache populated with the arguments that are going to be
- /// listed before they’re actually listed, if the options demand it.
- fn git_options(options: &Options, args: &[&OsStr]) -> Option<GitCache> {
- if options.should_scan_for_git() {
- Some(args.iter().map(PathBuf::from).collect())
- }
- else {
- None
- }
- }
- fn ignore_cache(options: &Options) -> Option<IgnoreCache> {
- use fs::filter::GitIgnore;
- match options.filter.git_ignore {
- GitIgnore::CheckAndIgnore => Some(IgnoreCache::new()),
- GitIgnore::Off => None,
- }
- }
- impl<'args, 'w, W: Write + 'w> Exa<'args, 'w, W> {
- pub fn from_args<I>(args: I, writer: &'w mut W) -> Result<Exa<'args, 'w, W>, Misfire>
- where I: Iterator<Item=&'args OsString> {
- Options::parse(args, &LiveVars).map(move |(options, mut args)| {
- debug!("Dir action from arguments: {:#?}", options.dir_action);
- debug!("Filter from arguments: {:#?}", options.filter);
- debug!("View from arguments: {:#?}", options.view.mode);
- // List the current directory by default, like ls.
- // This has to be done here, otherwise git_options won’t see it.
- if args.is_empty() {
- args = vec![ OsStr::new(".") ];
- }
- let git = git_options(&options, &args);
- let ignore = ignore_cache(&options);
- Exa { options, writer, args, git, ignore }
- })
- }
- pub fn run(&mut self) -> IOResult<i32> {
- let mut files = Vec::new();
- let mut dirs = Vec::new();
- let mut exit_status = 0;
- for file_path in &self.args {
- match File::from_args(PathBuf::from(file_path), None, None) {
- Err(e) => {
- exit_status = 2;
- writeln!(stderr(), "{:?}: {}", file_path, e)?;
- },
- Ok(f) => {
- if f.points_to_directory() && !self.options.dir_action.treat_dirs_as_files() {
- match f.to_dir() {
- Ok(d) => dirs.push(d),
- Err(e) => writeln!(stderr(), "{:?}: {}", file_path, e)?,
- }
- }
- else {
- files.push(f);
- }
- },
- }
- }
- // We want to print a directory’s name before we list it, *except* in
- // the case where it’s the only directory, *except* if there are any
- // files to print as well. (It’s a double negative)
- let no_files = files.is_empty();
- let is_only_dir = dirs.len() == 1 && no_files;
- self.options.filter.filter_argument_files(&mut files);
- self.print_files(None, files)?;
- self.print_dirs(dirs, no_files, is_only_dir, exit_status)
- }
- fn print_dirs(&mut self, dir_files: Vec<Dir>, mut first: bool, is_only_dir: bool, exit_status: i32) -> IOResult<i32> {
- for dir in dir_files {
- // Put a gap between directories, or between the list of files and
- // the first directory.
- if first {
- first = false;
- }
- else {
- writeln!(self.writer)?;
- }
- if !is_only_dir {
- let mut bits = Vec::new();
- escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
- writeln!(self.writer, "{}:", ANSIStrings(&bits))?;
- }
- let mut children = Vec::new();
- for file in dir.files(self.options.filter.dot_filter, self.ignore.as_ref()) {
- match file {
- Ok(file) => children.push(file),
- Err((path, e)) => writeln!(stderr(), "[{}: {}]", path.display(), e)?,
- }
- };
- self.options.filter.filter_child_files(&mut children);
- self.options.filter.sort_files(&mut children);
- if let Some(recurse_opts) = self.options.dir_action.recurse_options() {
- let depth = dir.path.components().filter(|&c| c != Component::CurDir).count() + 1;
- if !recurse_opts.tree && !recurse_opts.is_too_deep(depth) {
- let mut child_dirs = Vec::new();
- for child_dir in children.iter().filter(|f| f.is_directory() && !f.is_all_all) {
- match child_dir.to_dir() {
- Ok(d) => child_dirs.push(d),
- Err(e) => writeln!(stderr(), "{}: {}", child_dir.path.display(), e)?,
- }
- }
- self.print_files(Some(&dir), children)?;
- match self.print_dirs(child_dirs, false, false, exit_status) {
- Ok(_) => (),
- Err(e) => return Err(e),
- }
- continue;
- }
- }
- self.print_files(Some(&dir), children)?;
- }
- Ok(exit_status)
- }
- /// Prints the list of files using whichever view is selected.
- /// For various annoying logistical reasons, each one handles
- /// printing differently...
- fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File>) -> IOResult<()> {
- if !files.is_empty() {
- let View { ref mode, ref colours, ref style } = self.options.view;
- match *mode {
- Mode::Lines(ref opts) => {
- let r = lines::Render { files, colours, style, opts };
- r.render(self.writer)
- }
- Mode::Grid(ref opts) => {
- let r = grid::Render { files, colours, style, opts };
- r.render(self.writer)
- }
- Mode::Details(ref opts) => {
- let filter = &self.options.filter;
- let recurse = self.options.dir_action.recurse_options();
- let r = details::Render { dir, files, colours, style, opts, filter, recurse };
- r.render(self.git.as_ref(), self.ignore.as_ref(), self.writer)
- }
- Mode::GridDetails(ref opts) => {
- let grid = &opts.grid;
- let filter = &self.options.filter;
- let details = &opts.details;
- let row_threshold = opts.row_threshold;
- let r = grid_details::Render { dir, files, colours, style, grid, details, filter, row_threshold };
- r.render(self.git.as_ref(), self.writer)
- }
- }
- }
- else {
- Ok(())
- }
- }
- }
|