~erutuon/enwiktionary-translations-server

8824bc05edfa004f5bf410becf33a493de155f13 — Erutuon 2 years ago 1a30579
allow lookup by language name
M Cargo.lock => Cargo.lock +7 -0
@@ 337,6 337,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"

[[package]]
name = "bimap"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b"

[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 620,6 626,7 @@ name = "enwiktionary-translations-server"
version = "0.1.0"
dependencies = [
 "async-std",
 "bimap",
 "bstr",
 "lexopt",
 "regex",

M Cargo.toml => Cargo.toml +1 -0
@@ 7,6 7,7 @@ edition = "2021"

[dependencies]
async-std = { version = "1.10.0", features = ["attributes"] }
bimap = "0.6.2"
bstr = "0.2.17"
lexopt = "0.2.0"
regex = { version = "1.5.4", default-features = false, features = ["std", "perf"] }

M src/by_language.rs => src/by_language.rs +8 -3
@@ 1,3 1,4 @@
use bimap::BiHashMap;
use serde::{Deserialize, Serialize};

use crate::{


@@ 8,7 9,7 @@ use crate::{
    },
};

use std::{collections::HashMap, fmt::Write};
use std::fmt::Write;

use serde_with::rust::display_fromstr::deserialize as deserialize_fromstr;



@@ 51,6 52,10 @@ impl_args! {

    type Row = ByLanguageRow;

    fn language_code(&mut self) -> Option<&mut String> {
        Some(&mut self.language)
    }

    fn cache_key(&self) -> Option<crate::CacheKey> {
        let mut arg = self.clone();
        arg.id = arg.id.or(Some(RowId::StartId(1)));


@@ 81,7 86,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let language = &self.language;
        write!(


@@ 96,7 101,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row: &Self::Row,
        _: &HashMap<String, String>,
        _: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let Self::Row {
            title,

M src/by_term_and_language.rs => src/by_term_and_language.rs +8 -3
@@ 1,3 1,4 @@
use bimap::BiHashMap;
use serde::{Deserialize, Serialize};
use unicode_normalization::UnicodeNormalization;



@@ 10,7 11,7 @@ use crate::{
    },
};

use std::{collections::HashMap, fmt::Write};
use std::fmt::Write;

impl_args! {
    #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Hash)]


@@ 26,6 27,10 @@ impl_args! {
        self
    }

    fn language_code(&mut self) -> Option<&mut String> {
        Some(&mut self.language)
    }

    fn get_rows(&self, db: &rusqlite::Connection) -> rusqlite::Result<Vec<Self::Row>> {
        let sql = "select title, part_of_speech, part_of_speech_index, translation_section_index, definition,
                transliteration, automated_transliteration, transcription


@@ 41,7 46,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let Self { language, term, .. } = &self;
        write!(


@@ 58,7 63,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row: &Self::Row,
        _: &HashMap<String, String>,
        _: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        macro_rules! write_cell {
            ($field:expr) => {

M src/entry_name.rs => src/entry_name.rs +8 -3
@@ 1,3 1,4 @@
use bimap::BiHashMap;
use serde::Deserialize;

use crate::{


@@ 9,7 10,7 @@ use crate::{
    },
};

use std::{collections::HashMap, fmt::Write};
use std::fmt::Write;

#[macro_export]
macro_rules! impl_row_with_optional_language {


@@ 58,6 59,10 @@ impl_args! {

    type Row = EntryNameRow;

    fn language_code(&mut self) -> Option<&mut String> {
        self.language.as_mut()
    }

    fn get_rows(&self, db: &rusqlite::Connection) -> rusqlite::Result<Vec<Self::Row>> {
        let EntryNameArgs {
            entry_name,


@@ 90,7 95,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let EntryNameArgs {
            entry_name,


@@ 118,7 123,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row: &Self::Row,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let EntryNameRow {
            title,

M src/leaderboard.rs => src/leaderboard.rs +3 -3
@@ 1,9 1,9 @@
use std::{
    collections::HashMap,
    fmt::{Display, Write},
    str::FromStr,
};

use bimap::BiHashMap;
use serde::Deserialize;
use serde_with::{DeserializeFromStr, SerializeDisplay};



@@ 83,7 83,7 @@ impl Args for LeaderboardArgs {
        &self,
        writer: &mut String,
        row_count: usize,
        _: &HashMap<String, String>,
        _: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        match self.r#type {
            Leaderboard::Language => write!(


@@ 101,7 101,7 @@ impl Args for LeaderboardArgs {
        &self,
        writer: &mut String,
        row: &Self::Row,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        macro_rules! write_cell {
            ($field:expr) => {

M src/long_words.rs => src/long_words.rs +7 -3
@@ 7,9 7,9 @@ use crate::{
    },
};

use std::collections::HashMap;
use std::fmt::Write;

use bimap::BiHashMap;
use serde::Deserialize;

impl_args! {


@@ 20,6 20,10 @@ impl_args! {

    type Row = LongWordRow;

    fn language_code(&mut self) -> Option<&mut String> {
        self.language.as_mut()
    }

    fn get_rows(&self, db: &rusqlite::Connection) -> rusqlite::Result<Vec<Self::Row>> {
        let language = &self.language;
        macro_rules! make_query {


@@ 48,7 52,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let language = &self.language;
        if let Some(language) = language {


@@ 71,7 75,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row: &Self::Row,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let LongWordRow {
            grapheme_length_of_longest_word,

M src/main.rs => src/main.rs +20 -3
@@ 5,6 5,7 @@ use async_std::{
    path::{Path, PathBuf},
    sync::{Mutex, RwLock},
};
use bimap::BiHashMap;
use bstr::ByteSlice;
use regex::{Captures, Regex, Replacer};
use rusqlite::{functions::FunctionFlags, Connection, OpenFlags};


@@ 85,7 86,7 @@ pub struct State {
    db: Arc<Mutex<Connection>>,
    cache: Arc<RwLock<HashMap<CacheKey, Arc<CacheValue>>>>,
    template: Arc<Mutex<Template>>,
    language_code_to_name: Arc<HashMap<String, String>>,
    language_code_to_name: Arc<BiHashMap<String, String>>,
}

impl State {


@@ 145,11 146,27 @@ impl State {
        }
    }

    fn get_language_map(db: &Connection) -> rusqlite::Result<HashMap<String, String>> {
    fn get_language_map(db: &Connection) -> rusqlite::Result<BiHashMap<String, String>> {
        let mut statement = db.prepare("select code, name from language;")?;
        let rows = statement.query_map([], |row| Ok((row.get("code")?, row.get("name")?)))?;
        rows.collect()
    }

    /// Returns `Some(name)` if `language_code_or_name` might be a name
    /// (contains anything besides ASCII lowercase letters or hyphens)
    /// and is found in `self.language_code_to_name`.
    fn get_language_code<'a: 'b, 'b>(&'a self, language_code_or_name: &'b str) -> Option<&'a str> {
        if language_code_or_name
            .bytes()
            .all(|b| b == b'-' || b.is_ascii_lowercase())
        {
            None
        } else {
            self.language_code_to_name
                .get_by_right(language_code_or_name)
                .map(String::as_str)
        }
    }
}

struct Template {


@@ 203,7 220,7 @@ async fn main() -> tide::Result<()> {

    let language_code_to_name = State::get_language_map(&db).unwrap_or_else(|e| {
        eprintln!("Failed to get language map:\n{}", tide::Error::from(e));
        HashMap::new()
        BiHashMap::new()
    });
    let db = Arc::new(Mutex::new(db));
    let cache = Arc::new(RwLock::new(HashMap::new()));

M src/query.rs => src/query.rs +18 -5
@@ 1,3 1,4 @@
use bimap::BiHashMap;
use rusqlite::Connection;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tide::{http::mime, log::debug, Response, StatusCode};


@@ 7,7 8,7 @@ use crate::{
    CacheKey, CacheValue, State,
};

use std::{borrow::Cow, collections::HashMap, fmt::Debug};
use std::{borrow::Cow, fmt::Debug};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum Format {


@@ 139,7 140,15 @@ pub trait TableResponse: Sized + Send + Sync + Serialize {
    async fn respond(req: tide::Request<State>) -> tide::Result {
        let generated_in = GeneratedIn::new();

        let args: Self::Args = match req.query().map(Self::Args::normalize) {
        let args: Self::Args = match req.query().map(|mut args: Self::Args| {
            if let Some(language) = args.language_code() {
                if let Some(code) = req.state().get_language_code(language) {
                    language.clear();
                    language.push_str(code);
                }
            }
            args.normalize()
        }) {
            Ok(q) => q,
            Err(_) => {
                return Ok(Response::builder(StatusCode::BadRequest)


@@ 198,6 207,10 @@ pub trait Args: Clone + Into<CacheKey> {
        self
    }

    fn language_code(&mut self) -> Option<&mut String> {
        None
    }

    fn cache_key(&self) -> Option<crate::CacheKey> {
        Some(self.clone().without_format().into())
    }


@@ 212,7 225,7 @@ pub trait Args: Clone + Into<CacheKey> {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result;

    fn write_next_and_prev(&self, _rows: &[Self::Row], _writer: &mut String) -> std::fmt::Result {


@@ 222,13 235,13 @@ pub trait Args: Clone + Into<CacheKey> {
        &self,
        writer: &mut String,
        row: &Self::Row,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result;

    fn make_table(
        &self,
        rows: &[Self::Row],
        language_code_to_name: &HashMap<String, String>,
        language_code_to_name: &BiHashMap<String, String>,
    ) -> tide::Result<String> {
        let mut body = String::new();
        body.push_str("<table><caption>");

M src/transliteration.rs => src/transliteration.rs +8 -3
@@ 1,3 1,4 @@
use bimap::BiHashMap;
use serde::Deserialize;
use unicode_normalization::UnicodeNormalization;



@@ 10,7 11,7 @@ use crate::{
    },
};

use std::{collections::HashMap, fmt::Write};
use std::fmt::Write;

impl_args! {
    #[derive(Clone, Deserialize, Debug, PartialEq, Eq, Hash)]


@@ 26,6 27,10 @@ impl_args! {
        self
    }

    fn language_code(&mut self) -> Option<&mut String> {
        self.language.as_mut()
    }

    fn get_rows(&self, db: &rusqlite::Connection) -> rusqlite::Result<Vec<Self::Row>> {
        macro_rules! make_query {
            ($where1:literal, $where2:literal) => {


@@ 62,7 67,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row_count: usize,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let TransliterationArgs {
            transliteration,


@@ 90,7 95,7 @@ impl_args! {
        &self,
        writer: &mut String,
        row: &Self::Row,
        language_map: &HashMap<String, String>,
        language_map: &BiHashMap<String, String>,
    ) -> std::fmt::Result {
        let Self::Row {
            title,

M src/utils.rs => src/utils.rs +4 -4
@@ 1,10 1,10 @@
use std::{
    collections::HashMap,
    fmt::{Display, Write},
    time::Instant,
};

use async_std::path::Path;
use bimap::BiHashMap;
use tide::{http::mime::HTML, Body, Response, StatusCode};

use crate::{by_language::ByLanguageArgs, EMPTY_FIELD};


@@ 376,7 376,7 @@ impl<'a> Display for Romanization<'a> {
/// Links to the `/by-language` page for the given language.
pub fn by_language_link<'a, 'b: 'a>(
    language: &'a str,
    language_map: &'b HashMap<String, String>,
    language_map: &'b BiHashMap<String, String>,
) -> Link<String, LanguageDisplay<'a>> {
    Link {
        href: format!(


@@ 398,10 398,10 @@ pub struct LanguageDisplay<'a> {
}

impl<'a> LanguageDisplay<'a> {
    pub fn from_map<'b: 'a>(language_map: &'b HashMap<String, String>, code: &'a str) -> Self {
    pub fn from_map<'b: 'a>(language_map: &'b BiHashMap<String, String>, code: &'a str) -> Self {
        Self {
            code,
            name: language_map.get(code).map(|name| name.as_str()),
            name: language_map.get_by_left(code).map(|name| name.as_str()),
        }
    }
}