1#![cfg_attr(not(feature = "std"), no_std)]
2
3pub extern crate alloc;
4
5#[macro_export]
7macro_rules! language_code {
8 (
9 length = $length:tt;
10 $( #[$meta:meta] )*
11 $pub:vis enum $name:ident {
12 $(
13 $( #[$variant_meta:meta] )*
14 $variant:ident,
15 )+
16 }
17 ) => {
18 $(#[$meta])*
19 $pub enum $name {
20 $(
21 $( #[$variant_meta] )*
22 $variant,
23 )+
24 Other($crate::alloc::boxed::Box<str>),
25 }
26
27 impl $name {
29 pub const VARS: &'static [$name] = &[
30 $(
31 $name::$variant,
32 )+
33 ];
34 }
35
36 paste::paste! {
38 impl ::core::str::FromStr for $name {
39 type Err = $crate::error::ParseError;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 match s {
43 $(
44 ::core::stringify!($variant) | ::core::stringify!([<$variant:upper>]) => Ok(Self::$variant),
45 )+
46 s if s.len() == $length => Ok(Self::Other(s.into())),
47 s => Err($crate::error::ParseError::Invalid(s.into()))
48 }
49 }
50 }
51 }
52
53 impl ::core::fmt::Display for $name {
55 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
56 match self {
57 $(
58 Self::$variant => ::core::write!(f, "{}", ::core::stringify!($variant)),
59 )+
60 Self::Other(s) => ::core::write!(f, "{}", s)
61 }
62 }
63 }
64
65 impl ::core::cmp::PartialEq for $name {
67 fn eq(&self, other: &Self) -> bool {
68 $crate::alloc::format!("{}", self) == $crate::alloc::format!("{}", other)
69 }
70 }
71
72 impl ::core::cmp::Eq for $name {
73 }
74
75 impl_macros::impl_partial_eq_str_for_display! { str, $name }
77 impl_macros::impl_partial_eq_str_for_display! { &'a str, $name }
78 impl_macros::impl_partial_eq_str_for_display! { $crate::alloc::borrow::Cow<'a, str>, $name }
79 impl_macros::impl_partial_eq_str_for_display! { $crate::alloc::string::String, $name }
80
81 #[cfg(feature = "std")]
83 impl ::std::hash::Hash for $name {
84 fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
85 $crate::alloc::format!("{}", self).hash(state);
86 }
87 }
88
89 #[cfg(feature = "serde")]
91 impl<'de> ::serde::Deserialize<'de> for $name {
92 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
93 where
94 D: ::serde::Deserializer<'de>,
95 {
96 use ::core::str::FromStr as _;
97
98 let s = $crate::alloc::boxed::Box::<str>::deserialize(deserializer)?;
99 Self::from_str(&s).map_err(::serde::de::Error::custom)
100 }
101 }
102
103 #[cfg(feature = "serde")]
105 impl ::serde::Serialize for $name {
106 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
107 where
108 S: ::serde::Serializer,
109 {
110 use $crate::alloc::string::ToString as _;
111
112 self.to_string().serialize(serializer)
113 }
114 }
115 };
116}
117
118#[macro_export]
120macro_rules! language_tag {
121 (
122 $( #[$meta:meta] )*
123 $pub:vis struct $name:ident {
124 $( #[$language_code_meta:meta] )*
125 $language_code_pub:vis $language_code_name:ident : $language_code_ty:ty,
126 $( #[$country_code_meta:meta] )*
127 $country_code_pub:vis $country_code_name:ident : Option<$country_code_ty:ty>,
128 }
129 ) => {
130 $(#[$meta])*
131 $pub struct $name {
132 $( #[$language_code_meta:meta] )*
133 $language_code_pub $language_code_name: $language_code_ty,
134 $( #[$country_code_meta:meta] )*
135 $country_code_pub $country_code_name: Option<$country_code_ty>,
136 }
137
138 impl $name {
140 pub fn new(language_code: $language_code_ty, country_code: Option<$country_code_ty>) -> Self {
141 Self {
142 language_code,
143 country_code,
144 }
145 }
146 }
147
148 impl ::core::str::FromStr for $name {
150 type Err = $crate::error::LanguageTagParseError;
151
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 let language_code_s = s.chars().take_while(|x| x != &'-' && x != &'_')
154 .collect::<$crate::alloc::string::String>();
155 let language_code = language_code_s.parse::<$language_code_ty>()
156 .map_err(|_| $crate::error::LanguageTagParseError::LanguageCodeInvalid(language_code_s.as_str().into()))?;
157
158 let country_code = if s.len() > language_code_s.len() + 1 {
159 let country_code_s = &s[language_code_s.len() + 1..];
160 let country_code = country_code_s.parse::<$country_code_ty>()
161 .map_err(|_| $crate::error::LanguageTagParseError::CountryCodeInvalid(country_code_s.into()))?;
162 Some(country_code)
163 } else {
164 None
165 };
166
167 Ok(Self::new(language_code, country_code))
168 }
169 }
170
171 impl ::core::fmt::Display for $name {
173 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
174 if let Some(country_code) = &self.country_code {
175 ::core::write!(f, "{}-{}", &self.language_code, country_code)
176 } else {
177 ::core::write!(f, "{}", &self.language_code)
178 }
179 }
180 }
181
182 impl ::core::cmp::PartialEq for $name {
184 fn eq(&self, other: &Self) -> bool {
185 $crate::alloc::format!("{}", self) == $crate::alloc::format!("{}", other)
186 }
187 }
188
189 impl ::core::cmp::Eq for $name {
190 }
191
192 impl_macros::impl_partial_eq_str_for_display! { str, $name }
194 impl_macros::impl_partial_eq_str_for_display! { &'a str, $name }
195 impl_macros::impl_partial_eq_str_for_display! { $crate::alloc::borrow::Cow<'a, str>, $name }
196 impl_macros::impl_partial_eq_str_for_display! { $crate::alloc::string::String, $name }
197
198 #[cfg(feature = "std")]
200 impl ::std::hash::Hash for $name {
201 fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
202 $crate::alloc::format!("{}", self).hash(state);
203 }
204 }
205
206 #[cfg(feature = "serde")]
208 impl<'de> ::serde::Deserialize<'de> for $name {
209 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
210 where
211 D: ::serde::Deserializer<'de>,
212 {
213 use ::core::str::FromStr as _;
214
215 let s = $crate::alloc::boxed::Box::<str>::deserialize(deserializer)?;
216 Self::from_str(&s).map_err(::serde::de::Error::custom)
217 }
218 }
219
220 #[cfg(feature = "serde")]
222 impl ::serde::Serialize for $name {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: ::serde::Serializer,
226 {
227 use $crate::alloc::string::ToString as _;
228
229 self.to_string().serialize(serializer)
230 }
231 }
232 };
233}
234
235pub mod error;
237
238pub mod iso639_1;
240
241pub use iso639_1::{LanguageCode, LanguageTag};