rust_expect/auto_config/
locale.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone, Default)]
7pub struct LocaleInfo {
8 pub language: Option<String>,
10 pub territory: Option<String>,
12 pub codeset: Option<String>,
14 pub modifier: Option<String>,
16}
17
18impl LocaleInfo {
19 #[must_use]
21 pub fn parse(locale: &str) -> Self {
22 let mut info = Self::default();
23
24 if locale.is_empty() || locale == "C" || locale == "POSIX" {
26 info.language = Some("C".to_string());
27 return info;
28 }
29
30 let mut remaining = locale;
31
32 if let Some(at_pos) = remaining.rfind('@') {
34 info.modifier = Some(remaining[at_pos + 1..].to_string());
35 remaining = &remaining[..at_pos];
36 }
37
38 if let Some(dot_pos) = remaining.rfind('.') {
40 info.codeset = Some(remaining[dot_pos + 1..].to_string());
41 remaining = &remaining[..dot_pos];
42 }
43
44 if let Some(under_pos) = remaining.rfind('_') {
46 info.territory = Some(remaining[under_pos + 1..].to_string());
47 remaining = &remaining[..under_pos];
48 }
49
50 if !remaining.is_empty() {
52 info.language = Some(remaining.to_string());
53 }
54
55 info
56 }
57
58 #[must_use]
60 pub fn is_utf8(&self) -> bool {
61 self.codeset.as_ref().is_some_and(|c| {
62 let c = c.to_lowercase().replace('-', "");
63 c == "utf8"
64 })
65 }
66}
67
68impl std::fmt::Display for LocaleInfo {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 if let Some(ref lang) = self.language {
71 write!(f, "{lang}")?;
72 }
73 if let Some(ref territory) = self.territory {
74 write!(f, "_{territory}")?;
75 }
76 if let Some(ref codeset) = self.codeset {
77 write!(f, ".{codeset}")?;
78 }
79 if let Some(ref modifier) = self.modifier {
80 write!(f, "@{modifier}")?;
81 }
82 Ok(())
83 }
84}
85
86#[must_use]
88pub fn detect_locale() -> LocaleInfo {
89 let locale = std::env::var("LC_ALL")
91 .or_else(|_| std::env::var("LANG"))
92 .unwrap_or_default();
93
94 LocaleInfo::parse(&locale)
95}
96
97#[must_use]
99pub fn locale_env() -> HashMap<String, String> {
100 let vars = [
101 "LANG",
102 "LC_ALL",
103 "LC_CTYPE",
104 "LC_NUMERIC",
105 "LC_TIME",
106 "LC_COLLATE",
107 "LC_MONETARY",
108 "LC_MESSAGES",
109 "LC_PAPER",
110 "LC_NAME",
111 "LC_ADDRESS",
112 "LC_TELEPHONE",
113 "LC_MEASUREMENT",
114 "LC_IDENTIFICATION",
115 ];
116
117 let mut result = HashMap::new();
118 for var in vars {
119 if let Ok(value) = std::env::var(var) {
120 result.insert(var.to_string(), value);
121 }
122 }
123 result
124}
125
126#[must_use]
128pub fn is_utf8_environment() -> bool {
129 detect_locale().is_utf8()
130}
131
132#[must_use]
134pub fn utf8_environment() -> HashMap<String, String> {
135 let mut env = HashMap::new();
136 env.insert("LANG".to_string(), "en_US.UTF-8".to_string());
137 env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
138 env
139}
140
141#[must_use]
143pub fn force_utf8_env() -> HashMap<String, String> {
144 let mut env = locale_env();
145 env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
146 env
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn parse_full_locale() {
155 let info = LocaleInfo::parse("en_US.UTF-8");
156 assert_eq!(info.language, Some("en".to_string()));
157 assert_eq!(info.territory, Some("US".to_string()));
158 assert_eq!(info.codeset, Some("UTF-8".to_string()));
159 assert!(info.is_utf8());
160 }
161
162 #[test]
163 fn parse_locale_with_modifier() {
164 let info = LocaleInfo::parse("de_DE.UTF-8@euro");
165 assert_eq!(info.language, Some("de".to_string()));
166 assert_eq!(info.territory, Some("DE".to_string()));
167 assert_eq!(info.modifier, Some("euro".to_string()));
168 }
169
170 #[test]
171 fn parse_c_locale() {
172 let info = LocaleInfo::parse("C");
173 assert_eq!(info.language, Some("C".to_string()));
174 assert!(!info.is_utf8());
175 }
176
177 #[test]
178 fn locale_to_string() {
179 let info = LocaleInfo {
180 language: Some("en".to_string()),
181 territory: Some("US".to_string()),
182 codeset: Some("UTF-8".to_string()),
183 modifier: None,
184 };
185 assert_eq!(info.to_string(), "en_US.UTF-8");
186 }
187}