use crate::config::LIB_CFG;
#[cfg(feature = "lang-detection")]
use crate::config::TMPL_FILTER_GET_LANG_ALL;
use crate::error::LibCfgError;
#[cfg(feature = "lang-detection")]
use lingua;
#[cfg(feature = "lang-detection")]
use lingua::IsoCode639_1;
use parking_lot::RwLock;
use std::collections::BTreeMap;
use std::env;
#[cfg(feature = "lang-detection")]
use std::str::FromStr;
#[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)]
#[allow(dead_code)]
pub(crate) enum FilterGetLang {
Disabled,
AllLanguages,
#[cfg(feature = "lang-detection")]
SomeLanguages(Vec<IsoCode639_1>),
#[cfg(not(feature = "lang-detection"))]
SomeLanguages(Vec<String>),
Error(LibCfgError),
}
#[derive(Debug)]
#[allow(dead_code)]
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 {
#[cfg(not(test))]
fn default() -> Self {
DEFAULT_SETTINGS
}
#[cfg(test)]
fn default() -> Self {
let mut settings = DEFAULT_SETTINGS;
settings.author = String::from("testuser");
settings.lang = String::from("ab_AB");
settings.extension_default = String::from("md");
settings
}
}
#[cfg(not(test))]
pub(crate) static SETTINGS: RwLock<Settings> = RwLock::new(DEFAULT_SETTINGS);
#[cfg(test)]
lazy_static::lazy_static! {
pub(crate) static ref SETTINGS: RwLock<Settings> = RwLock::new(DEFAULT_SETTINGS);
}
pub(crate) fn force_lang_setting(lang: Option<String>) {
let mut settings = SETTINGS.write();
if let Some(l) = lang {
settings.lang = l;
}
settings.filter_get_lang = FilterGetLang::Disabled;
log::trace!(
"`SETTINGS` updated after `force_lang_setting()`:\n{:#?}",
settings
);
}
pub fn update_settings() -> Result<(), LibCfgError> {
let mut settings = SETTINGS.write();
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 (reading config + env. vars.):\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())
})
});
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_recursive();
lib_cfg.filename.extension_default.to_string()
}
};
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)]);
}
};
settings.lang = lang;
}
#[cfg(feature = "lang-detection")]
fn update_filter_get_lang_setting(settings: &mut Settings) {
let lib_cfg = LIB_CFG.read_recursive();
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());
LibCfgError::ParseLanguageCode {
language_code: l.into(),
all_langs,
}
})
})
.collect::<Result<Vec<IsoCode639_1>, LibCfgError>>()
{
Ok(mut iso_codes) => {
if all_languages_selected {
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);
}
}
}
}
settings.filter_get_lang = match iso_codes.len() {
0 => FilterGetLang::Disabled,
1 => FilterGetLang::Error(LibCfgError::NotEnoughLanguageCodes {
language_code: iso_codes[0].to_string(),
}),
_ => FilterGetLang::SomeLanguages(iso_codes),
}
}
}
Err(e) =>
{
settings.filter_get_lang = FilterGetLang::Error(e);
}
}
}
#[cfg(not(feature = "lang-detection"))]
fn update_filter_get_lang_setting(settings: &mut Settings) {
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_recursive();
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());
}
};
}
settings.filter_map_lang_btmap = Some(btm);
}
#[cfg(feature = "lang-detection")]
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() {
settings.filter_get_lang = FilterGetLang::Disabled;
settings.filter_map_lang_btmap = None;
log::debug!(
"Empty env. var. `{}` disables the `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());
LibCfgError::ParseLanguageCode {
language_code: l.into(),
all_langs,
}
})
})
.collect::<Result<Vec<IsoCode639_1>, LibCfgError>>()
{
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 {
settings.filter_get_lang = FilterGetLang::AllLanguages;
} else {
settings.filter_get_lang = match iso_codes.len() {
0 => FilterGetLang::Disabled,
1 => FilterGetLang::Error(LibCfgError::NotEnoughLanguageCodes {
language_code: iso_codes[0].to_string(),
}),
_ => FilterGetLang::SomeLanguages(iso_codes),
}
}
settings.filter_map_lang_btmap = Some(hm);
}
Err(e) =>
{
settings.filter_get_lang = FilterGetLang::Error(e);
}
}
}
}
#[cfg(not(feature = "lang-detection"))]
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() {
settings.filter_get_lang = FilterGetLang::Disabled;
settings.filter_map_lang_btmap = None;
log::debug!(
"Ignoring the env. var. `{}`. The `lang-detection` feature \
is not included in this build.",
ENV_VAR_TPNOTE_LANG_DETECTION
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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, "md");
}
#[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();
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();
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();
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();
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();
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();
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();
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();
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();
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();
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();
settings.lang = "xy-XY".to_string();
env::set_var(ENV_VAR_TPNOTE_LANG_DETECTION, "en-GB, fr");
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 fr ");
} 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();
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();
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());
}
}