1pub use tinystr::ParseError;
2use unic_langid::subtags;
3
4use crate::LangID;
5#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct ID {
20 pub language: u64,
21 pub script: Option<u32>,
22 pub region: Option<u32>,
23}
24
25impl core::fmt::Display for ID {
26 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27 let Self {
28 language,
29 script,
30 region,
31 } = self;
32
33 match () {
34 #[cfg(not(feature = "compact_str"))]
35 () => write!(
36 f,
37 r##"RawID::new({language}, {script:?}, {region:?}).into_lang_id()"##,
38 ),
39 #[cfg(feature = "compact_str")]
40 () => write!(
41 f,
42 r##"/*{}*/ RawID::new({language}, {script:?}, {region:?}).into_lang_id()"##,
43 self.to_bcp47(),
44 ),
45 }
46 }
47}
48
49impl ID {
50 pub const fn new(language: u64, script: Option<u32>, region: Option<u32>) -> Self {
51 Self {
52 language,
53 script,
54 region,
55 }
56 }
57 pub const fn try_from_str(
74 language: &str,
75 script: &str,
76 region: &str,
77 ) -> Result<Self, ParseError> {
78 use tinystr::TinyAsciiStr as TinyStr;
79 type Language = TinyStr<8>;
80 type Tiny4 = TinyStr<4>;
81
82 let language = match language.is_empty() {
83 true => return Err(ParseError::ContainsNull),
84 _ => {
85 let tmp = match Language::try_from_str(language) {
86 Ok(s) => s,
87 Err(e) => return Err(e),
88 };
89 u64::from_le_bytes(*tmp.all_bytes())
90 }
91 };
92
93 let script = match script.is_empty() {
94 true => None,
95 _ => Some({
96 let str = match Tiny4::try_from_str(script) {
97 Ok(s) => s,
98 Err(e) => return Err(e),
99 };
100 u32::from_le_bytes(*str.all_bytes())
101 }),
102 };
103
104 let region = match region.is_empty() {
105 true => None,
106 _ => Some({
107 let str = match Tiny4::try_from_str(region) {
108 Ok(s) => s,
109 Err(e) => return Err(e),
110 };
111 u32::from_le_bytes(*str.all_bytes())
112 }),
113 };
114
115 let id = ID::new(language, script, region);
116
117 Ok(id)
118 }
119
120 #[cfg(feature = "compact_str")]
121 pub fn to_bcp47(&self) -> compact_str::CompactString {
135 use compact_str::{CompactString, format_compact};
136 let id = self.into_lang_id();
137
138 let LangID {
139 language,
140 script,
141 region,
142 ..
143 } = id;
144
145 let empty_str = || CompactString::const_new("");
146 format_compact!(
147 "{language}{}{}",
148 match script {
149 Some(s) => format_compact!("-{s}"),
150 _ => empty_str(),
151 },
152 match region {
153 Some(s) => format_compact!("-{s}"),
154 _ => empty_str(),
155 }
156 )
157 }
158
159 #[inline]
164 pub const fn into_lang_id(self) -> LangID {
165 unsafe {
166 let language = subtags::Language::from_raw_unchecked(self.language);
167
168 let script = match self.script {
170 Some(s) => Some(subtags::Script::from_raw_unchecked(s)),
171 _ => None,
172 };
173
174 let region = match self.region {
175 Some(r) => Some(subtags::Region::from_raw_unchecked(r)),
176 _ => None,
177 };
178
179 LangID::from_raw_parts_unchecked(language, script, region, None)
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_build_lzh() -> Result<(), ParseError> {
190 let lzh = ID::try_from_str("lzh", "Hant", "")?;
191 assert_eq!(
192 lzh.to_string(),
193 "RawID::new(6847084, Some(1953390920), None).into_lang_id()"
194 );
195 Ok(())
196 }
197
198 #[test]
199 #[cfg(feature = "compact_str")]
200 fn test_to_bcp47() -> Result<(), ParseError> {
201 let id = ID::try_from_str("es", "Latn", "419")?;
202 let bcp47 = id.to_bcp47();
203 assert_eq!(bcp47, "es-Latn-419");
204 Ok(())
206 }
207}