| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- #![warn(deprecated_in_future)]
- #![warn(future_incompatible)]
- #![warn(nonstandard_style)]
- #![warn(rust_2018_compatibility)]
- #![warn(rust_2018_idioms)]
- #![warn(trivial_casts, trivial_numeric_casts)]
- #![warn(unused)]
- #![warn(clippy::all, clippy::pedantic)]
- #![allow(clippy::cast_precision_loss)]
- #![allow(clippy::cast_possible_truncation)]
- #![allow(clippy::cast_possible_wrap)]
- #![allow(clippy::cast_sign_loss)]
- #![allow(clippy::enum_glob_use)]
- #![allow(clippy::map_unwrap_or)]
- #![allow(clippy::match_same_arms)]
- #![allow(clippy::module_name_repetitions)]
- #![allow(clippy::non_ascii_literal)]
- #![allow(clippy::option_if_let_else)]
- #![allow(clippy::too_many_lines)]
- #![allow(clippy::unused_self)]
- #![allow(clippy::upper_case_acronyms)]
- #![allow(clippy::wildcard_imports)]
- use std::env;
- use std::ffi::{OsStr, OsString};
- use std::io::{self, Write, ErrorKind};
- use std::path::{Component, PathBuf};
- use ansi_term::{ANSIStrings, Style};
- use log::*;
- use crate::fs::{Dir, File};
- use crate::fs::feature::git::GitCache;
- use crate::fs::filter::GitIgnore;
- use crate::options::{Options, Vars, vars, OptionsResult};
- use crate::output::{escape, lines, grid, grid_details, details, View, Mode};
- use crate::theme::Theme;
- mod fs;
- mod info;
- mod logger;
- mod options;
- mod output;
- mod theme;
- fn main() {
- use std::process::exit;
- #[cfg(unix)]
- unsafe {
- libc::signal(libc::SIGPIPE, libc::SIG_DFL);
- }
- logger::configure(env::var_os(vars::EXA_DEBUG));
- #[cfg(windows)]
- if let Err(e) = ansi_term::enable_ansi_support() {
- warn!("Failed to enable ANSI support: {}", e);
- }
- let args: Vec<_> = env::args_os().skip(1).collect();
- match Options::parse(args.iter().map(std::convert::AsRef::as_ref), &LiveVars) {
- OptionsResult::Ok(options, mut input_paths) => {
- // List the current directory by default.
- // (This has to be done here, otherwise git_options won’t see it.)
- if input_paths.is_empty() {
- input_paths = vec![ OsStr::new(".") ];
- }
- let git = git_options(&options, &input_paths);
- let writer = io::stdout();
- let console_width = options.view.width.actual_terminal_width();
- let theme = options.theme.to_theme(console_width.is_some());
- let exa = Exa { options, writer, input_paths, theme, console_width, git };
- match exa.run() {
- Ok(exit_status) => {
- exit(exit_status);
- }
- Err(e) if e.kind() == ErrorKind::BrokenPipe => {
- warn!("Broken pipe error: {}", e);
- exit(exits::SUCCESS);
- }
- Err(e) => {
- eprintln!("{}", e);
- exit(exits::RUNTIME_ERROR);
- }
- }
- }
- OptionsResult::Help(help_text) => {
- print!("{}", help_text);
- }
- OptionsResult::Version(version_str) => {
- print!("{}", version_str);
- }
- OptionsResult::InvalidOptions(error) => {
- eprintln!("exa: {}", error);
- if let Some(s) = error.suggestion() {
- eprintln!("{}", s);
- }
- exit(exits::OPTIONS_ERROR);
- }
- }
- }
- /// The main program wrapper.
- pub struct Exa<'args> {
- /// List of command-line options, having been successfully parsed.
- pub options: Options,
- /// The output handle that we write to.
- pub writer: io::Stdout,
- /// List of the free command-line arguments that should correspond to file
- /// names (anything that isn’t an option).
- pub input_paths: Vec<&'args OsStr>,
- /// The theme that has been configured from the command-line options and
- /// environment variables. If colours are disabled, this is a theme with
- /// every style set to the default.
- pub theme: Theme,
- /// The detected width of the console. This is used to determine which
- /// view to use.
- pub console_width: Option<usize>,
- /// 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>,
- }
- /// 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> {
- env::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
- }
- }
- impl<'args> Exa<'args> {
- /// # Errors
- ///
- /// Will return `Err` if printing to stderr fails.
- pub fn run(mut self) -> io::Result<i32> {
- debug!("Running with options: {:#?}", self.options);
- let mut files = Vec::new();
- let mut dirs = Vec::new();
- let mut exit_status = 0;
- for file_path in &self.input_paths {
- match File::from_args(PathBuf::from(file_path), None, None) {
- Err(e) => {
- exit_status = 2;
- writeln!(io::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!(io::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) -> io::Result<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!(&mut self.writer)?;
- }
- if ! is_only_dir {
- let mut bits = Vec::new();
- escape(dir.path.display().to_string(), &mut bits, Style::default(), Style::default());
- writeln!(&mut self.writer, "{}:", ANSIStrings(&bits))?;
- }
- let mut children = Vec::new();
- let git_ignore = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
- for file in dir.files(self.options.filter.dot_filter, self.git.as_ref(), git_ignore) {
- match file {
- Ok(file) => children.push(file),
- Err((path, e)) => writeln!(io::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!(io::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.
- fn print_files(&mut self, dir: Option<&Dir>, files: Vec<File<'_>>) -> io::Result<()> {
- if files.is_empty() {
- return Ok(());
- }
- let theme = &self.theme;
- let View { ref mode, ref file_style, .. } = self.options.view;
- match (mode, self.console_width) {
- (Mode::Grid(ref opts), Some(console_width)) => {
- let filter = &self.options.filter;
- let r = grid::Render { files, theme, file_style, opts, console_width, filter };
- r.render(&mut self.writer)
- }
- (Mode::Grid(_), None) |
- (Mode::Lines, _) => {
- let filter = &self.options.filter;
- let r = lines::Render { files, theme, file_style, filter };
- r.render(&mut self.writer)
- }
- (Mode::Details(ref opts), _) => {
- let filter = &self.options.filter;
- let recurse = self.options.dir_action.recurse_options();
- let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
- let git = self.git.as_ref();
- let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
- r.render(&mut self.writer)
- }
- (Mode::GridDetails(ref opts), Some(console_width)) => {
- let grid = &opts.grid;
- let details = &opts.details;
- let row_threshold = opts.row_threshold;
- let filter = &self.options.filter;
- let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
- let git = self.git.as_ref();
- let r = grid_details::Render { dir, files, theme, file_style, grid, details, filter, row_threshold, git_ignoring, git, console_width };
- r.render(&mut self.writer)
- }
- (Mode::GridDetails(ref opts), None) => {
- let opts = &opts.to_details_options();
- let filter = &self.options.filter;
- let recurse = self.options.dir_action.recurse_options();
- let git_ignoring = self.options.filter.git_ignore == GitIgnore::CheckAndIgnore;
- let git = self.git.as_ref();
- let r = details::Render { dir, files, theme, file_style, opts, recurse, filter, git_ignoring, git };
- r.render(&mut self.writer)
- }
- }
- }
- }
- mod exits {
- /// Exit code for when exa runs OK.
- pub const SUCCESS: i32 = 0;
- /// Exit code for when there was at least one I/O error during execution.
- pub const RUNTIME_ERROR: i32 = 1;
- /// Exit code for when the command-line options are invalid.
- pub const OPTIONS_ERROR: i32 = 3;
- }
|