autoruby 0.2.0

Easily generate furigana for various document formats
use std::{collections::BTreeMap, io::BufRead};

use serde::{Deserialize, Serialize};

use crate::parse::{self, dictionary_line};

pub const DOWNLOAD_URL: &str =
    "https://github.com/Doublevil/JmdictFurigana/releases/latest/download/JmdictFurigana.txt";

pub async fn download() -> Result<String, reqwest::Error> {
    reqwest::get(DOWNLOAD_URL).await.unwrap().text().await
}

pub struct FrequencyEntry<'a> {
    kanji_element: &'a str,
    kanji_common: bool,
    reading_element: &'a str,
    reading_common: bool,
}

pub fn frequency_entries() -> impl Iterator<Item = FrequencyEntry<'static>> {
    jmdict::entries().flat_map(|e| {
        e.kanji_elements().flat_map(move |k| {
            e.reading_elements().map(move |r| FrequencyEntry {
                kanji_element: k.text,
                kanji_common: k.priority.is_common(),
                reading_element: r.text,
                reading_common: r.priority.is_common(),
            })
        })
    })
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadingSpan {
    pub start_index: u8,
    pub end_index: u8,
    pub text: String,
}

impl From<parse::ReadingSpan<'_>> for ReadingSpan {
    fn from(value: parse::ReadingSpan<'_>) -> Self {
        Self {
            start_index: value.start_index,
            end_index: value.end_index,
            text: value.text.to_string(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextEntry {
    pub text: String,
    pub text_is_common: bool,
    pub reading: String,
    pub reading_is_common: bool,
    pub reading_spans: Vec<ReadingSpan>,
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Debug, Clone)]
pub struct DictionaryIndex {
    text: String,
    reading: String,
}

impl<T: AsRef<str>> From<T> for DictionaryIndex {
    fn from(s: T) -> Self {
        Self {
            text: s.as_ref().to_string(),
            reading: Default::default(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dictionary(BTreeMap<DictionaryIndex, TextEntry>);

impl Dictionary {
    pub fn lookup_word<'s>(&'s self, word: &str) -> Vec<&'s TextEntry> {
        self.0
            .range(DictionaryIndex::from(word)..)
            .map_while(|(DictionaryIndex { text, .. }, entry)| (text == word).then_some(entry))
            .collect()
    }

    pub fn lookup_prefixed<'s>(&'s self, prefix: &'s str) -> Vec<&'s TextEntry> {
        self.0
            .range(DictionaryIndex::from(prefix)..)
            .map_while(|(DictionaryIndex { text, .. }, entry)| {
                text.starts_with(prefix).then_some(entry)
            })
            .collect()
    }
}

pub fn build(input_reader: impl BufRead) -> Dictionary {
    let mut tree = input_reader
        .lines()
        .map(|line| {
            let line = line.unwrap();
            let (_, entry) = dictionary_line(&line).unwrap();
            (
                DictionaryIndex {
                    text: entry.text.to_string(),
                    reading: entry.reading.to_string(),
                },
                TextEntry {
                    text: entry.text.to_string().into(),
                    text_is_common: false,
                    reading: entry.reading.to_string(),
                    reading_is_common: false,
                    reading_spans: entry.reading_spans.into_iter().map(Into::into).collect(),
                },
            )
        })
        .collect::<BTreeMap<_, _>>();

    frequency_entries().for_each(|freq| {
        if let Some(e) = tree.get_mut(&DictionaryIndex {
            text: freq.kanji_element.into(),
            reading: freq.reading_element.into(),
        }) {
            e.reading_is_common = freq.reading_common;
            e.text_is_common = freq.kanji_common;
        }
    });

    Dictionary(tree)
}