typetui 0.2.0

A terminal-based typing test.
Documentation
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};

#[allow(clippy::too_many_lines)]
fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = PathBuf::from(&out_dir).join("content.rs");
    let mut file = File::create(&dest_path).unwrap();

    writeln!(file, "// Auto-generated by build.rs - DO NOT EDIT").unwrap();
    writeln!(file, "mod generated_content {{").unwrap();
    writeln!(file, "    #![allow(clippy::all)]").unwrap();
    writeln!(file, "    #![allow(clippy::pedantic)]").unwrap();
    writeln!(file).unwrap();

    let text_dir = Path::new("text");
    let mut text_languages = Vec::new();
    let mut text_words: HashMap<String, Vec<String>> = HashMap::new();

    if text_dir.exists() {
        for entry in fs::read_dir(text_dir).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            if path.is_file() && path.extension().is_some_and(|e| e == "txt") {
                let lang = path.file_stem().unwrap().to_str().unwrap().to_string();
                let content = fs::read_to_string(&path).unwrap();
                let words: Vec<String> = content
                    .lines()
                    .map(|s| s.trim().to_string())
                    .filter(|s| !s.is_empty())
                    .collect();
                text_languages.push(lang.clone());
                text_words.insert(lang, words);
            }
        }
    }

    let code_dir = Path::new("code");
    let mut code_languages = Vec::new();
    let mut code_snippets: HashMap<String, Vec<String>> = HashMap::new();

    if code_dir.exists() {
        for entry in fs::read_dir(code_dir).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();
            if path.is_dir() {
                let lang = path.file_name().unwrap().to_str().unwrap().to_string();
                let mut snippets = Vec::new();

                for file_entry in fs::read_dir(&path).unwrap() {
                    let file_entry = file_entry.unwrap();
                    let file_path = file_entry.path();
                    if file_path.is_file() {
                        let content = fs::read_to_string(&file_path).unwrap();
                        snippets.push(content);
                    }
                }

                if !snippets.is_empty() {
                    code_languages.push(lang.clone());
                    code_snippets.insert(lang, snippets);
                }
            }
        }
    }

    writeln!(file, "    pub const TEXT_LANGUAGES: &[&str] = &[").unwrap();
    for lang in &text_languages {
        writeln!(file, "        \"{lang}\",").unwrap();
    }
    writeln!(file, "    ];").unwrap();
    writeln!(file).unwrap();

    writeln!(file, "    pub const CODE_LANGUAGES: &[&str] = &[").unwrap();
    for lang in &code_languages {
        writeln!(file, "        \"{lang}\",").unwrap();
    }
    writeln!(file, "    ];").unwrap();
    writeln!(file).unwrap();

    writeln!(
        file,
        "    pub fn get_word_list(language: &str) -> Vec<&'static str> {{"
    )
    .unwrap();
    writeln!(file, "        match language {{").unwrap();
    for (lang, words) in &text_words {
        let words_str = words
            .iter()
            .map(|w| format!("\"{}\"", w.replace('"', "\\\"")))
            .collect::<Vec<_>>()
            .join(", ");
        writeln!(file, "            \"{lang}\" => vec![{words_str}],").unwrap();
    }
    writeln!(file, "            _ => vec![]").unwrap();
    writeln!(file, "        }}").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file).unwrap();

    writeln!(
        file,
        "    pub fn get_snippets(language: &str) -> Vec<&'static str> {{"
    )
    .unwrap();
    writeln!(file, "        match language {{").unwrap();
    for (lang, snippets) in &code_snippets {
        let snippets_str = snippets
            .iter()
            .map(|s| format!("r#####\"{}\"#####", s.replace("#####", "")))
            .collect::<Vec<_>>()
            .join(", ");
        writeln!(file, "            \"{lang}\" => vec![{snippets_str}],").unwrap();
    }
    writeln!(file, "            _ => vec![]").unwrap();
    writeln!(file, "        }}").unwrap();
    writeln!(file, "    }}").unwrap();
    writeln!(file, "}}").unwrap();

    println!("cargo:rerun-if-changed=text/");
    println!("cargo:rerun-if-changed=code/");
}