sd 1.0.0

An intuitive find & replace CLI
use std::{fs::File, io::prelude::*, path::PathBuf};

use crate::{Error, Replacer, Result};

use is_terminal::IsTerminal;

#[derive(Debug)]
pub(crate) enum Source {
    Stdin,
    Files(Vec<PathBuf>),
}

pub(crate) struct App {
    replacer: Replacer,
    source: Source,
}

impl App {
    fn stdin_replace(&self, is_tty: bool) -> Result<()> {
        let mut buffer = Vec::with_capacity(256);
        let stdin = std::io::stdin();
        let mut handle = stdin.lock();
        handle.read_to_end(&mut buffer)?;

        let stdout = std::io::stdout();
        let mut handle = stdout.lock();

        handle.write_all(&if is_tty {
            self.replacer.replace_preview(&buffer)
        } else {
            self.replacer.replace(&buffer)
        })?;

        Ok(())
    }

    pub(crate) fn new(source: Source, replacer: Replacer) -> Self {
        Self { source, replacer }
    }
    pub(crate) fn run(&self, preview: bool) -> Result<()> {
        let is_tty = std::io::stdout().is_terminal();

        match (&self.source, preview) {
            (Source::Stdin, true) => self.stdin_replace(is_tty),
            (Source::Stdin, false) => self.stdin_replace(is_tty),
            (Source::Files(paths), false) => {
                use rayon::prelude::*;

                let failed_jobs: Vec<_> = paths
                    .par_iter()
                    .filter_map(|p| {
                        if let Err(e) = self.replacer.replace_file(p) {
                            Some((p.to_owned(), e))
                        } else {
                            None
                        }
                    })
                    .collect();

                if failed_jobs.is_empty() {
                    Ok(())
                } else {
                    let failed_jobs =
                        crate::error::FailedJobs::from(failed_jobs);
                    Err(Error::FailedProcessing(failed_jobs))
                }
            }
            (Source::Files(paths), true) => {
                let stdout = std::io::stdout();
                let mut handle = stdout.lock();
                let print_path = paths.len() > 1;

                paths.iter().try_for_each(|path| {
                    if Replacer::check_not_empty(File::open(path)?).is_err() {
                        return Ok(());
                    }
                    let file =
                        unsafe { memmap2::Mmap::map(&File::open(path)?)? };
                    if self.replacer.has_matches(&file) {
                        if print_path {
                            writeln!(
                                handle,
                                "----- FILE {} -----",
                                path.display()
                            )?;
                        }

                        handle
                            .write_all(&self.replacer.replace_preview(&file))?;
                        writeln!(handle)?;
                    }

                    Ok(())
                })
            }
        }
    }
}