wiki_api_macros/
lib.rs

1use std::collections::{BTreeMap, HashMap};
2
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::quote;
6use serde::{de::IgnoredAny, Deserialize, Serialize};
7use syn::{parse_macro_input, Ident, LitStr};
8
9#[derive(Debug, Serialize, Deserialize)]
10struct WLanguage {
11    code: String,
12    name: String,
13    #[serde(skip_serializing)]
14    #[allow(dead_code)]
15    site: Vec<IgnoredAny>,
16    dir: String,
17    localname: String,
18    #[serde(skip)]
19    identifier: Option<Ident>,
20}
21
22#[derive(Debug, Deserialize)]
23struct SiteMatrix {
24    #[serde(skip_serializing)]
25    #[allow(dead_code)]
26    count: usize,
27    #[serde(flatten)]
28    languages: HashMap<String, WLanguage>,
29}
30#[derive(Debug, Deserialize)]
31struct SiteMatrixWrapper {
32    #[serde(rename = "sitematrix")]
33    site_matrix: SiteMatrix,
34}
35
36#[proc_macro]
37pub fn parse_languages(input: TokenStream) -> TokenStream {
38    let input = parse_macro_input!(input as LitStr).value();
39    let sitematrix: SiteMatrixWrapper = serde_json::from_str(&input).unwrap();
40    let languages =
41        sitematrix
42            .site_matrix
43            .languages
44            .into_iter()
45            .fold(BTreeMap::new(), |mut acc, e| {
46                let mut e = e.1;
47                let name = if acc.contains_key(&e.localname) {
48                    normalize_string(&(e.localname.clone() + &e.code))
49                } else {
50                    normalize_string(&e.localname)
51                };
52                e.identifier = Some(Ident::new(&name, Span::call_site()));
53                acc.insert(name, e);
54                acc
55            });
56    let mut variants = quote!();
57    let mut language_data_arms = quote!();
58    let mut from_str_arms = quote!();
59    let mut array_def = quote!();
60    for (_key, value) in languages {
61        let ident = value.identifier.clone().unwrap();
62        let en_name = value.localname.clone();
63        let lang_name = value.name.clone();
64        let lang_code = value.code.clone();
65
66        let en_name_lowercase = en_name.to_lowercase();
67        let lang_name_lowercase = lang_name.to_lowercase();
68        let lang_code_lowercase = lang_code.to_lowercase();
69
70        variants = quote! {
71            #variants
72            #ident,
73        };
74        language_data_arms = quote! {
75            #language_data_arms
76            Language::#ident => (#en_name, #lang_name, #lang_code),
77        };
78        from_str_arms = quote! {
79            #from_str_arms
80            #lang_code_lowercase | #lang_name_lowercase | #en_name_lowercase => Some(Language::#ident),
81        };
82        array_def = quote! {
83            #array_def
84            Language::#ident,
85        }
86    }
87
88    let expanded = quote! {
89        use serde::{Serialize, Deserialize};
90        use std::str::FromStr;
91        use std::convert::TryFrom;
92
93        #[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
94        #[serde(try_from = "String")]
95        pub enum Language{
96            Unknown,
97            #variants
98        }
99
100        impl Language{
101            /// Returns the data associated to the language. It's formatted like this:
102            /// (Language name in English, Local Language name, Language Code)
103            fn language_data(&self) -> (&str, &str, &str) {
104                match self {
105                    Language::Unknown => ("UNKNOWN", "UNKNOWN", "UNKNOWN"),
106                    #language_data_arms
107                }
108            }
109            /// Returns the English name of the language
110            pub fn name(&self) -> &str {
111                self.language_data().0
112            }
113
114            /// Returns the local name of the language
115            pub fn local_name(&self) -> &str {
116                self.language_data().1
117            }
118
119            /// Returns the language code
120            pub fn code(&self) -> &str {
121                self.language_data().2
122            }
123        }
124
125        impl FromStr for Language {
126            type Err = ParseLanguageError;
127            fn from_str(from: &str) -> Result<Self, Self::Err> {
128                match from.to_lowercase().as_ref() {
129                    #from_str_arms
130                    _ => None,
131                }.ok_or(ParseLanguageError(from.to_string()))
132            }
133        }
134
135        impl TryFrom<String> for Language {
136            type Error = ParseLanguageError;
137            fn try_from(val: String) -> Result<Self, Self::Error> {
138                Self::from_str(val.as_str())
139            }
140        }
141
142        #[derive(Debug)]
143        pub struct ParseLanguageError(String);
144
145        impl std::fmt::Display for ParseLanguageError {
146            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147                f.pad(
148                    &format!("error parsing langugage: '{}' is an unknown language", self.0)
149                )
150            }
151        }
152
153        impl std::error::Error for ParseLanguageError {}
154
155
156        pub static LANGUAGES: &[Language] = &[#array_def];
157
158        impl Default for Language {
159            fn default() -> Self {
160                Language::Unknown
161            }
162        }
163    };
164    proc_macro::TokenStream::from(expanded)
165}
166
167fn normalize_string(input: &str) -> String {
168    input
169        .chars()
170        .enumerate()
171        .map(|(index, c)| {
172            if c.is_alphabetic() {
173                if index == 0 {
174                    c.to_uppercase().to_string()
175                } else {
176                    c.to_string()
177                }
178            } else {
179                "".to_string()
180            }
181        })
182        .collect()
183}