use crate::config::LIB_CFG;
use crate::config::TMPL_FILTER_GET_LANG_ALL;
use crate::error::ConfigError;
use lingua;
#[cfg(feature = "lang-detection")]
use lingua::IsoCode639_1;
use std::collections::BTreeMap;
use std::{env, mem, str::FromStr, sync::RwLock};
#[cfg(target_family = "windows")]
use windows_sys::Win32::Globalization::GetUserDefaultLocaleName;
#[cfg(target_family = "windows")]
use windows_sys::Win32::System::SystemServices::LOCALE_NAME_MAX_LENGTH;
pub const ENV_VAR_TPNOTE_EXTENSION_DEFAULT: &str = "TPNOTE_EXTENSION_DEFAULT";
pub const ENV_VAR_TPNOTE_LANG: &str = "TPNOTE_LANG";
pub const ENV_VAR_TPNOTE_LANG_DETECTION: &str = "TPNOTE_LANG_DETECTION";
pub const ENV_VAR_TPNOTE_USER: &str = "TPNOTE_USER";
const ENV_VAR_LOGNAME: &str = "LOGNAME";
const ENV_VAR_USERNAME: &str = "USERNAME";
const ENV_VAR_USER: &str = "USER";
#[cfg(not(target_family = "windows"))]
const ENV_VAR_LANG: &str = "LANG";
#[derive(Debug)]
pub(crate) enum FilterGetLang {
Disabled,
AllLanguages,
#[cfg(feature = "lang-detection")]
SomeLanguages(Vec<IsoCode639_1>),
#[cfg(not(feature = "lang-detection"))]
SomeLanguages(Vec<String>),
Error(ConfigError),
}
#[derive(Debug)]
pub(crate) struct Settings {
pub author: String,
pub lang: String,
pub extension_default: String,
pub filter_get_lang: FilterGetLang,
pub filter_map_lang_btmap: Option<BTreeMap<String, String>>,
}
const DEFAULT_SETTINGS: Settings = Settings {
author: String::new(),
lang: String::new(),
extension_default: String::new(),
filter_get_lang: FilterGetLang::Disabled,
filter_map_lang_btmap: None,
};
impl Default for Settings {
fn default() -> Self {
DEFAULT_SETTINGS
}
}
pub(crate) static SETTINGS: RwLock<Settings> = RwLock::new(DEFAULT_SETTINGS);
pub(crate) fn force_lang_setting(lang: &str) {
let lang = lang.trim();
let mut settings = SETTINGS.write().unwrap();
if lang != "-" {
let _ = mem::replace(&mut settings.lang, lang.to_string());
}
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Disabled);
}
pub fn update_settings() -> Result<(), ConfigError> {
let mut settings = SETTINGS.write().unwrap();
update_author_setting(&mut settings);
update_extension_default_setting(&mut settings);
update_lang_setting(&mut settings);
update_filter_get_lang_setting(&mut settings);
update_filter_map_lang_btmap_setting(&mut settings);
update_env_lang_detection(&mut settings);
log::trace!("`SETTINGS` updated:\n{:#?}", settings);
if let FilterGetLang::Error(e) = &settings.filter_get_lang {
Err(e.clone())
} else {
Ok(())
}
}
fn update_author_setting(settings: &mut Settings) {
let author = env::var(ENV_VAR_TPNOTE_USER).unwrap_or_else(|_| {
env::var(ENV_VAR_LOGNAME).unwrap_or_else(|_| {
env::var(ENV_VAR_USERNAME)
.unwrap_or_else(|_| env::var(ENV_VAR_USER).unwrap_or_default())
})
});
let _ = mem::replace(&mut settings.author, author);
}
fn update_extension_default_setting(settings: &mut Settings) {
let ext = match env::var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT) {
Ok(ed_env) if !ed_env.is_empty() => ed_env,
Err(_) | Ok(_) => {
let lib_cfg = LIB_CFG.read().unwrap();
lib_cfg.filename.extension_default.to_string()
}
};
let _ = mem::replace(&mut settings.extension_default, ext);
}
fn update_lang_setting(settings: &mut Settings) {
let mut lang = String::new();
let tpnotelang = env::var(ENV_VAR_TPNOTE_LANG).ok();
#[cfg(not(target_family = "windows"))]
if let Some(tpnotelang) = tpnotelang {
lang = tpnotelang;
} else {
if let Ok(lang_env) = env::var(ENV_VAR_LANG) {
if !lang_env.is_empty() {
let mut language = "";
let mut territory = "";
if let Some((l, lang_env)) = lang_env.split_once('_') {
language = l;
if let Some((t, _codeset)) = lang_env.split_once('.') {
territory = t;
}
}
lang = language.to_string();
lang.push('-');
lang.push_str(territory);
}
}
}
#[cfg(target_family = "windows")]
if let Some(tpnotelang) = tpnotelang {
lang = tpnotelang;
} else {
let mut buf = [0u16; LOCALE_NAME_MAX_LENGTH as usize];
let len = unsafe { GetUserDefaultLocaleName(buf.as_mut_ptr(), buf.len() as i32) };
if len > 0 {
lang = String::from_utf16_lossy(&buf[..((len - 1) as usize)]);
}
};
let _ = mem::replace(&mut settings.lang, lang);
}
#[cfg(feature = "lang-detection")]
fn update_filter_get_lang_setting(settings: &mut Settings) {
let lib_cfg = LIB_CFG.read().unwrap();
let mut all_languages_selected = false;
match lib_cfg
.tmpl
.filter_get_lang
.iter()
.filter(|&l| {
if l == TMPL_FILTER_GET_LANG_ALL {
all_languages_selected = true;
false
} else {
true
}
})
.map(|l| {
IsoCode639_1::from_str(l).map_err(|_| {
let mut all_langs = lingua::Language::all()
.iter()
.map(|l| {
let mut s = l.iso_code_639_1().to_string();
s.push_str(", ");
s
})
.collect::<Vec<String>>();
all_langs.sort();
let mut all_langs = all_langs.into_iter().collect::<String>();
all_langs.truncate(all_langs.len() - ", ".len());
ConfigError::ParseLanguageCode {
language_code: l.into(),
all_langs,
}
})
})
.collect::<Result<Vec<IsoCode639_1>, ConfigError>>()
{
Ok(mut iso_codes) => {
if all_languages_selected {
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::AllLanguages);
} else {
if !settings.lang.is_empty() {
if let Some((lang_subtag, _)) = settings.lang.split_once('-') {
if let Ok(iso_code) = IsoCode639_1::from_str(lang_subtag) {
if !iso_codes.contains(&iso_code) {
iso_codes.push(iso_code);
}
}
}
}
if iso_codes.len() >= 2 {
let _ = mem::replace(
&mut settings.filter_get_lang,
FilterGetLang::SomeLanguages(iso_codes),
);
} else {
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Disabled);
}
}
}
Err(e) =>
{
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Error(e));
}
}
}
#[cfg(not(feature = "lang-detection"))]
fn update_filter_get_lang_setting(settings: &mut Settings) {
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Disabled);
}
fn update_filter_map_lang_btmap_setting(settings: &mut Settings) {
let mut btm = BTreeMap::new();
let lib_cfg = LIB_CFG.read().unwrap();
for l in &lib_cfg.tmpl.filter_map_lang {
if l.len() >= 2 {
btm.insert(l[0].to_string(), l[1].to_string());
};
}
if !settings.lang.is_empty() {
if let Some((lang_subtag, _)) = settings.lang.split_once('-') {
if !lang_subtag.is_empty() && !btm.contains_key(lang_subtag) {
btm.insert(lang_subtag.to_string(), settings.lang.to_string());
}
};
}
let _ = mem::replace(&mut settings.filter_map_lang_btmap, Some(btm));
}
fn update_env_lang_detection(settings: &mut Settings) {
if let Ok(env_var) = env::var(ENV_VAR_TPNOTE_LANG_DETECTION) {
if env_var.is_empty() {
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Disabled);
let _ = mem::replace(&mut settings.filter_map_lang_btmap, None);
log::debug!(
"Empty env. var. `{}` disables `lang-detection` feature.",
ENV_VAR_TPNOTE_LANG_DETECTION
);
return;
}
let mut hm: BTreeMap<String, String> = BTreeMap::new();
let mut all_languages_selected = false;
match env_var
.split(',')
.map(|t| {
let t = t.trim();
if let Some((lang_subtag, _)) = t.split_once('-') {
if !lang_subtag.is_empty() && !hm.contains_key(lang_subtag) {
hm.insert(lang_subtag.to_string(), t.to_string());
};
lang_subtag
} else {
t
}
})
.filter(|&l| {
if l == TMPL_FILTER_GET_LANG_ALL {
all_languages_selected = true;
false
} else {
true
}
})
.map(|l| {
IsoCode639_1::from_str(l.trim()).map_err(|_| {
let mut all_langs = lingua::Language::all()
.iter()
.map(|l| {
let mut s = l.iso_code_639_1().to_string();
s.push_str(", ");
s
})
.collect::<Vec<String>>();
all_langs.sort();
let mut all_langs = all_langs.into_iter().collect::<String>();
all_langs.truncate(all_langs.len() - ", ".len());
ConfigError::ParseLanguageCode {
language_code: l.into(),
all_langs,
}
})
})
.collect::<Result<Vec<IsoCode639_1>, ConfigError>>()
{
Ok(mut iso_codes) => {
if !settings.lang.is_empty() {
if let Some(lang_subtag) = settings.lang.split('-').next() {
if let Ok(iso_code) = IsoCode639_1::from_str(lang_subtag) {
if !iso_codes.contains(&iso_code) {
iso_codes.push(iso_code);
}
if lang_subtag != settings.lang && !hm.contains_key(lang_subtag) {
hm.insert(lang_subtag.to_string(), settings.lang.to_string());
}
}
}
}
if all_languages_selected {
let _ =
mem::replace(&mut settings.filter_get_lang, FilterGetLang::AllLanguages);
} else {
let _ = mem::replace(
&mut settings.filter_get_lang,
FilterGetLang::SomeLanguages(iso_codes),
);
}
let _ = mem::replace(&mut settings.filter_map_lang_btmap, Some(hm));
}
Err(e) =>
{
let _ = mem::replace(&mut settings.filter_get_lang, FilterGetLang::Error(e));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::FILENAME_EXTENSION_DEFAULT;
#[test]
fn test_update_author_setting() {
let mut settings = Settings::default();
env::set_var(ENV_VAR_LOGNAME, "testauthor");
update_author_setting(&mut settings);
assert_eq!(settings.author, "testauthor");
}
#[test]
fn test_update_extension_default_setting() {
let mut settings = Settings::default();
env::set_var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT, "markdown");
update_extension_default_setting(&mut settings);
assert_eq!(settings.extension_default, "markdown");
let mut settings = Settings::default();
std::env::remove_var(ENV_VAR_TPNOTE_EXTENSION_DEFAULT);
update_extension_default_setting(&mut settings);
assert_eq!(settings.extension_default, FILENAME_EXTENSION_DEFAULT);
}
#[test]
#[cfg(not(target_family = "windows"))]
fn test_update_lang_setting() {
let mut settings = Settings::default();
env::remove_var(ENV_VAR_TPNOTE_LANG);
env::set_var(ENV_VAR_LANG, "en_GB.UTF-8");
update_lang_setting(&mut settings);
assert_eq!(settings.lang, "en-GB");
let mut settings = Settings::default();
env::remove_var(ENV_VAR_TPNOTE_LANG);
env::set_var(ENV_VAR_LANG, "");
update_lang_setting(&mut settings);
assert_eq!(settings.lang, "");
let mut settings = Settings::default();
env::set_var(ENV_VAR_TPNOTE_LANG, "it-IT");
env::set_var(ENV_VAR_LANG, "en_GB.UTF-8");
update_lang_setting(&mut settings);
assert_eq!(settings.lang, "it-IT");
}
#[test]
fn test_update_filter_get_lang_setting() {
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
update_filter_get_lang_setting(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "en fr de ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "it-IT".to_string());
update_filter_get_lang_setting(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "en fr de it ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
}
#[test]
fn test_update_filter_map_lang_hmap_setting() {
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "it-IT".to_string());
update_filter_map_lang_btmap_setting(&mut settings);
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("et").unwrap(), "et-ET");
assert_eq!(output_filter_map_lang.get("it").unwrap(), "it-IT");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "it".to_string());
update_filter_map_lang_btmap_setting(&mut settings);
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("et").unwrap(), "et-ET");
assert_eq!(output_filter_map_lang.get("it"), None);
}
#[test]
fn test_update_env_lang_detection() {
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "fr-FR, de-DE, hu");
update_env_lang_detection(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "fr de hu en ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("fr").unwrap(), "fr-FR");
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, de-AT, en-US");
update_env_lang_detection(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "de de en ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-US");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, +all, en-US");
update_env_lang_detection(&mut settings);
assert!(matches!(
settings.filter_get_lang,
FilterGetLang::AllLanguages
));
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-US");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, de-AT, en");
update_env_lang_detection(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "de de en ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, +all, de-AT, en");
update_env_lang_detection(&mut settings);
assert!(matches!(
settings.filter_get_lang,
FilterGetLang::AllLanguages
));
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("de").unwrap(), "de-DE");
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
update_env_lang_detection(&mut settings);
assert!(matches!(settings.filter_get_lang, FilterGetLang::Disabled));
assert!(settings.filter_map_lang_btmap.is_none());
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "xy-XY".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "en-GB");
update_env_lang_detection(&mut settings);
if let FilterGetLang::SomeLanguages(ofgl) = settings.filter_get_lang {
let output_filter_get_lang = ofgl
.iter()
.map(|l| {
let mut l = l.to_string();
l.push_str(" ");
l
})
.collect::<String>();
assert_eq!(output_filter_get_lang, "en ");
} else {
panic!("Wrong variant: {:?}", settings.filter_get_lang);
}
let output_filter_map_lang = settings.filter_map_lang_btmap.unwrap();
assert_eq!(output_filter_map_lang.get("en").unwrap(), "en-GB");
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "de-DE, xy-XY");
update_env_lang_detection(&mut settings);
assert!(matches!(settings.filter_get_lang, FilterGetLang::Error(..)));
assert!(settings.filter_map_lang_btmap.is_none());
let mut settings = Settings::default();
let _ = mem::replace(&mut settings.lang, "en-GB".to_string());
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "");
update_env_lang_detection(&mut settings);
assert!(matches!(settings.filter_get_lang, FilterGetLang::Disabled));
assert!(settings.filter_map_lang_btmap.is_none());
}
}