diff-folders 0.0.1

This tool allows you to display differences in a list of compared folders in the terminal.
Documentation
use anyhow::Result;
use crossterm::{
    event::{self, Event, KeyCode},
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use diff_folders::{app::App, log::init_logger};
use scopeguard::defer;
use std::{
    env::args,
    io::{self, Write},
    path::{self, Path},
    str::FromStr,
};
use tui::{
    backend::{Backend, CrosstermBackend},
    Terminal,
};

fn main() -> Result<()> {
    if args().len() != 3 {
        panic!(
            "{} <old_dir|new_file> <new_dir|new_file>",
            args().next().unwrap()
        )
    }
    let (old_dir, new_dir) = parse_args();

    init_logger()?;
    setup_terminal()?;

    defer! {
        shutdown_terminal();
    }
    let mut terminal = start_terminal(io::stdout())?;

    let app = App::new(old_dir, new_dir);
    let res = run_app(&mut terminal, app);

    if let Err(err) = res {
        log::error!("{:?}", err)
    }
    Ok(())
}

fn parse_args() -> (String, String) {
    let mut args = args();
    args.next();
    let mut old_dir = args.next().unwrap();
    let mut new_dir = args.next().unwrap();
    if old_dir.ends_with(path::MAIN_SEPARATOR) {
        old_dir = old_dir[0..old_dir.len() - 1].to_string();
    }
    if new_dir.ends_with(path::MAIN_SEPARATOR) {
        new_dir = new_dir[0..new_dir.len() - 1].to_string();
    }
    let new_dir = String::from_str(
        Path::new(&new_dir)
            .canonicalize()
            .unwrap()
            .to_str()
            .unwrap(),
    )
    .unwrap();
    let old_dir = String::from_str(
        Path::new(&old_dir)
            .canonicalize()
            .unwrap()
            .to_str()
            .unwrap(),
    )
    .unwrap();
    (old_dir, new_dir)
}

fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
    loop {
        app.draw_terminal(terminal)?;
        if let Event::Key(key) = event::read()? {
            match key.code {
                KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
                _ => app.event(key.code),
            }
        }
    }
}

fn setup_terminal() -> Result<()> {
    enable_raw_mode()?;
    io::stdout().execute(EnterAlternateScreen)?;
    Ok(())
}

fn start_terminal<W: Write>(buf: W) -> io::Result<Terminal<CrosstermBackend<W>>> {
    let backend = CrosstermBackend::new(buf);
    let mut terminal = Terminal::new(backend)?;
    terminal.hide_cursor()?;
    terminal.clear()?;

    Ok(terminal)
}

fn shutdown_terminal() {
    let leave_screen = io::stdout().execute(LeaveAlternateScreen).map(|_f| ());

    if let Err(e) = leave_screen {
        log::error!("leave_screen failed:\n{e}\n");
    }

    let leave_raw_mode = disable_raw_mode();

    if let Err(e) = leave_raw_mode {
        log::error!("leave_raw_mode failed:\n{e}\n");
    }
}