dictate 0.9.0

CLI utility for looking up words in online dictionary
Documentation
use std::fmt::Write;
use std::{error::Error, process};

use clap::Parser;
use colored::control;
use dictate::{
    cache::Cache,
    charset,
    cli::{Cli, Command, When},
    client,
    entry::Entry,
};
use minus::Pager;
use tokio::fs::OpenOptions;

#[tokio::main]
async fn main() {
    run().await.unwrap_or_else(|e| {
        eprintln!("dictate: {}", e);
        process::exit(1);
    });
}

async fn run() -> Result<(), Box<dyn Error>> {
    let cli = Cli::parse();
    configure_color(&cli.color);

    let mut pager = Pager::new();
    let mut cache = Cache::open(OpenOptions::new().read(true).write(true).create(true)).await?;

    match cli.command {
        Command::Lookup { word } => {
            let entries = fetch_entries(&mut cache, &word).await?;

            for (i, entry) in entries.iter().enumerate() {
                if i > 0 {
                    writeln!(pager)?;
                }

                writeln!(pager, "{}", entry)?;
            }
        }

        Command::Clean { cache: cache_flag } => {
            if cache_flag {
                cache.clean().await?;
            }
        }

        Command::Complete { shell } => {
            let completion = match shell.as_str() {
                "bash" => include_str!(concat!(env!("OUT_DIR"), "/bash/dictate.bash")),
                "elvish" => include_str!(concat!(env!("OUT_DIR"), "/elvish/dictate.elv")),
                "fish" => include_str!(concat!(env!("OUT_DIR"), "/fish/dictate.fish")),
                "powershell" => {
                    include_str!(concat!(env!("OUT_DIR"), "/powershell/_dictate.ps1"))
                }
                "zsh" => include_str!(concat!(env!("OUT_DIR"), "/zsh/_dictate")),
                _ => return Err(format!("shell `{}` not supported", shell).into()),
            };

            println!("{}", completion);
        }
    }

    pager.set_run_no_overflow(true)?;
    minus::page_all(pager)?;

    Ok(())
}

fn configure_color(color: &When) {
    match color {
        When::Auto => (),
        When::Never => {
            control::set_override(false);
            charset::set_override(false);
        }
        When::Always => {
            control::set_override(true);
            charset::set_override(true);
        }
    };
}

async fn fetch_entries(cache: &mut Cache, word: &str) -> Result<Vec<Entry>, Box<dyn Error>> {
    let mut entries = cache.lookup_word(word).await?;
    if entries.is_empty() {
        entries = client::lookup_word(word).await?;
        cache.append(&mut entries.clone()).await?;
    }

    Ok(entries)
}