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 fn language_data(&self) -> (&str, &str, &str) {
104 match self {
105 Language::Unknown => ("UNKNOWN", "UNKNOWN", "UNKNOWN"),
106 #language_data_arms
107 }
108 }
109 pub fn name(&self) -> &str {
111 self.language_data().0
112 }
113
114 pub fn local_name(&self) -> &str {
116 self.language_data().1
117 }
118
119 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}