the-book-tui 0.1.2

A TUI application to load, read, and search The Rust Book.
use crossterm::{execute, terminal};
use indicatif::ProgressBar;
use reqwest::{blocking::Client, header::HeaderMap};
use serde::Deserialize;
use std::{
    fs::{self, File},
    io::{self, Write},
    path::PathBuf,
};

pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[derive(Deserialize, Debug)]
pub struct GitHubFile {
    pub path: String,
}

impl GitHubFile {
    fn to_rawfile_link(&self) -> String {
        let mut base = String::from("https://raw.githubusercontent.com/rust-lang/book/main/");
        base.push_str(self.path.as_str());
        base
    }
}

#[derive(Deserialize)]
struct GithubResponse {
    tree: Vec<GitHubFile>,
}

fn book_dir() -> PathBuf {
    match home::home_dir() {
        Some(path) => {
            let path = path.join(".the-book-tui/book");
            return path;
        }
        None => {
            panic!("Impossible to get your home dir! Please set your $HOME environment variable.")
        }
    }
}

fn get_structure() -> Result<Vec<(String, PathBuf)>> {
    let mut contents = Vec::new();
    let mut entries = fs::read_dir(book_dir())?
        .map(|res| res.map(|e| e.path()))
        .collect::<std::result::Result<Vec<_>, io::Error>>()?;

    entries.sort();

    for path in entries {
        if !path.is_dir() {
            contents.push((
                path.file_stem()
                    .unwrap()
                    .to_os_string()
                    .to_str()
                    .unwrap()
                    .to_string(),
                path,
            ));
        }
    }

    Ok(contents)
}

pub fn get_pages() -> Result<Vec<(String, PathBuf)>> {
    if book_dir().is_dir() {
        return get_structure();
    }

    println!("Book not found. Downloading...");

    let mut headers = HeaderMap::new();
    headers.insert("User-Agent", "the-book-tui".parse().unwrap());

    let client = Client::new();
    let  git_tree = client.get("https://api.github.com/repos/rust-lang/book/git/trees/21a2ed14f4480dab62438dcc1130291bebc65379?recursive=1")
        .headers(headers.clone())
        .send()?
        .json::<GithubResponse>()?
        .tree;

    let latest_version: Vec<&GitHubFile> = git_tree
        .iter()
        .filter(|f| f.path.starts_with("src/") && f.path.ends_with(".md"))
        .collect();

    let pb = ProgressBar::new(latest_version.iter().len() as u64);

    for f in latest_version.iter() {
        let title = f.path.split_once('/').unwrap().1;
        let rawfile_link = f.to_rawfile_link();
        let markdown = client
            .get(rawfile_link)
            .headers(headers.clone())
            .send()?
            .text()?;

        fs::create_dir_all(book_dir())?;
        let path = book_dir();
        let path = path.join(title);

        let mut file = File::create(path)?;
        file.write_all(markdown.as_bytes())?;

        pb.inc(1);
    }

    pb.finish();

    let mut stdout = io::stdout();
    execute!(stdout, terminal::Clear(terminal::ClearType::All)).unwrap();

    return get_structure();
}