use std::collections::HashMap; use std::env; use std::fs; use std::path::{Path, PathBuf}; // To get effective user id (EUID) so that setuid works use users::{get_effective_uid,get_user_by_uid}; // For home directory use users::os::unix::UserExt; use lazy_static::lazy_static; lazy_static! { static ref LOGLEVEL: LogLevel = LogLevel::from_env(); static ref LANG: Lang = Lang::from_env(); static ref TRANSLATIONS: HashMap = load_translations(); } /// Lang configures how to deal with translations. It has three possible values: /// None: translations return their own key name /// Some(code): where code is the language code for the JSON translation file /// JsonContext: debug output for Context as JSON /// File(PathBuf): fixed translation file (instead of language code) pub enum Lang { None, JsonContext, Some(String), File(PathBuf), } impl Lang { fn from_env() -> Lang { let lang = env::var("LANG").expect("$LANG not set in environment. Your machine is misconfigured!"); match lang.to_uppercase().as_str() { "NONE" => Lang::None, "JSON" => Lang::JsonContext, "C" => { // Special case: when no lang is specified, default to english Lang::Some("en".to_string()) }, _ => { let p = PathBuf::from(&lang); if p.is_file() { // LANG env variable contains a path to a full (JSON) file Lang::File(p) } else { Lang::Some(lang[0..2].to_string()) } } } } } pub type Context = HashMap; /// Returns a PathBuf containing the translations. Does not mean /// the translations are in there! /// First attempts to find $FORGEBUILDI18N from env, otherwise tries /// $HOME/.local/share/forgebuild/i18n or /usr/local/share/forgebuild/i18n fn find_translations() -> PathBuf { match env::var("FORGEBUILDI18N") { Ok(dir) => PathBuf::from(dir), Err(_) => { //let mut path = home_dir().expect("$HOME folder not found"); let owner = get_effective_uid(); let mut path = get_user_by_uid(owner).expect("Failed owner profile") .home_dir().to_path_buf(); path.push(".local/share/forgebuild/i18n"); println!("{}", path.to_str().unwrap()); if path.is_dir() { path } else { // Didn't find from env or in home directory, // return default /usr/share/local/forgebuild/i18n PathBuf::from("/usr/share/local/forgebuild/i18n") } } } } fn load_translations_from_file(path: &Path) -> HashMap { match fs::read_to_string(&path) { Ok(content) => { //let trans: HashMap = serde_json::from_str(&content).expect("Could not load translations"); //return trans match serde_json::from_str(&content) { Ok(trans) => trans, Err(e) => panic!("JSON ERROR: {}", e), } } Err(e) => { panic!("IO ERROR: {}", e); } } } fn load_translations() -> HashMap { match &*LANG { Lang::File(path) => { load_translations_from_file(&path) }, Lang::Some(lang) => { let mut path = find_translations(); if !path.is_dir() { panic!("Could not find translations in {:?}", path); } path.push(format!("{}.json", lang)); if !path.is_file() { panic!("Could not find translation file: {:?}", path); } load_translations_from_file(&path) }, _ => { HashMap::new() } } } fn trans(key: &str) -> String { match &*LANG { Lang::File(path) => { if let Some(t) = TRANSLATIONS.get(key) { t.to_string() } else { panic!("Missing translation for {} in translation file: {}", key, path.to_str().unwrap()) } }, Lang::Some(lang) => { if let Some(t) = TRANSLATIONS.get(key) { t.to_string() } else { panic!("Missing translation for {} in lang {}", key, lang) } }, Lang::JsonContext => String::new(), Lang::None => key.to_string(), } } fn trans_context(key: &str, context: &Context) -> String { match &*LANG { Lang::JsonContext => { // Serialize the context to JSON for debugging serde_json::to_string(context).expect("Failed to serialize to JSON") }, _ => expand(&trans(key), context) } } struct LogLevel { info: bool, debug: bool, error: bool, } impl LogLevel { fn from_env() -> LogLevel { // Default values let error = true; let mut info = true; let mut debug = false; let env_log = env::var("LOG").unwrap_or("info".to_string()); match env_log.to_lowercase().as_str() { "info" => {} "debug" => { debug = true; } "error" => { info = false; } _ => { // This happens before loglevel initialization // so we can't use warn function eprintln!( "$LOG level is incorrect: {} (can be: debug, info, error", env_log ); } } return LogLevel { info, debug, error }; } } /// Expands variables from the vars Context. If the /// context is empty, the string is returned untouched fn expand(msg: &str, vars: &Context) -> String { if vars.is_empty() { return msg.to_string(); } return vars .iter() .fold(msg.to_string(), |prev, (key, val)| prev.replace(key, val)); } pub fn info(msg: &str, vars: &Context) { if LOGLEVEL.info { println!("{}{}", trans("info"), trans_context(msg, vars)); } } pub fn error(msg: &str, vars: &Context) { if LOGLEVEL.error { eprintln!("{}{}", trans("error"), trans_context(msg, vars)); } } pub fn warn(msg: &str, vars: &Context) { if LOGLEVEL.error { eprintln!("{}{}", trans("warning"), trans_context(msg, vars)); } } pub fn debug(msg: &str, vars: &Context) { if LOGLEVEL.debug { println!("{}{}", trans("debug"), trans_context(msg, vars)); } }