1use tinystr::ParseError;
2use unic_langid::subtags;
3
4use crate::LangID;
5#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct RawID {
20 pub language: u64,
21 pub script: Option<u32>,
22 pub region: Option<u32>,
23}
24
25impl core::fmt::Display for RawID {
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 RawID {
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(
75 language: &str,
76 script: &str,
77 region: &str,
78 ) -> Result<Self, ParseError> {
79 use tinystr::TinyAsciiStr as TinyStr;
80 type Language = TinyStr<8>;
81 type Tiny4 = TinyStr<4>;
82
83 let language = match language.is_empty() {
84 true => return Err(ParseError::ContainsNull),
85 _ => {
86 let tmp = match Language::try_from_str(language) {
87 Ok(s) => s,
88 Err(e) => return Err(e),
89 };
90 u64::from_le_bytes(*tmp.all_bytes())
91 }
92 };
93
94 let script = match script.is_empty() {
95 true => None,
96 _ => Some({
97 let str = match Tiny4::try_from_str(script) {
98 Ok(s) => s,
99 Err(e) => return Err(e),
100 };
101 u32::from_le_bytes(*str.all_bytes())
102 }),
103 };
104
105 let region = match region.is_empty() {
106 true => None,
107 _ => Some({
108 let str = match Tiny4::try_from_str(region) {
109 Ok(s) => s,
110 Err(e) => return Err(e),
111 };
112 u32::from_le_bytes(*str.all_bytes())
113 }),
114 };
115
116 let id = RawID::new(language, script, region);
117
118 Ok(id)
119 }
120
121 #[cfg(feature = "compact_str")]
122 pub fn to_bcp47(&self) -> compact_str::CompactString {
136 use compact_str::{CompactString, format_compact};
137 let id = self.into_lang_id();
138
139 let LangID {
140 language,
141 script,
142 region,
143 ..
144 } = id;
145
146 let empty_str = || CompactString::const_new("");
147 format_compact!(
148 "{language}{}{}",
149 match script {
150 Some(s) => format_compact!("-{s}"),
151 _ => empty_str(),
152 },
153 match region {
154 Some(s) => format_compact!("-{s}"),
155 _ => empty_str(),
156 }
157 )
158 }
159
160 #[inline]
165 pub const fn into_lang_id(self) -> LangID {
166 unsafe {
167 let language = subtags::Language::from_raw_unchecked(self.language);
168
169 let script = match self.script {
171 Some(s) => Some(subtags::Script::from_raw_unchecked(s)),
172 _ => None,
173 };
174
175 let region = match self.region {
176 Some(r) => Some(subtags::Region::from_raw_unchecked(r)),
177 _ => None,
178 };
179
180 LangID::from_raw_parts_unchecked(language, script, region, None)
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 #[allow(unused_imports)]
188 use super::*;
189
190 #[test]
191 #[cfg(feature = "compact_str")]
192 fn test_build_lzh() -> Result<(), ParseError> {
193 let lzh = RawID::try_from_str("lzh", "Hant", "")?;
194 use compact_str::ToCompactString;
195
196 assert_eq!(
197 lzh.to_compact_string(),
198 "/*lzh-Hant*/ RawID::new(6847084, Some(1953390920), None).into_lang_id()"
199 );
200 Ok(())
201 }
202
203 #[test]
204 #[cfg(feature = "compact_str")]
205 fn test_to_bcp47() -> Result<(), ParseError> {
206 let id = RawID::try_from_str("es", "Latn", "419")?;
207 let bcp47 = id.to_bcp47();
208 assert_eq!(bcp47, "es-Latn-419");
209 Ok(())
211 }
212}