234 lines
6.9 KiB
Rust
234 lines
6.9 KiB
Rust
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<String, String> = 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<String, String>;
|
|
|
|
/// Finds the JSON translation files checking in order, relative to the program path:
|
|
/// - ../../../build/i18n
|
|
/// - ../../spec/i18n
|
|
/// - $HOME/.local/share/forgebuild/i18n
|
|
/// - /usr/share/forgebuild/i18n
|
|
/// If all of the above fails, but ../../spec/ folder exists (we are in a build.rs repo)
|
|
/// attempt to inialize the spec submodule which may have been forgotten on clone
|
|
fn find_translations() -> Option<PathBuf> {
|
|
let mut bindir = PathBuf::from(env::args().nth(0).expect("Argument 0 should contain the path to the program"));
|
|
bindir.pop();
|
|
|
|
let options = [
|
|
bindir.join("../../../build/i18n"),
|
|
bindir.join("../../spec/i18n"),
|
|
get_user_by_uid(get_effective_uid()).expect("Failed to get info about $USER").home_dir().join(".local/share/forgebuild/i18n"),
|
|
PathBuf::from("/usr/share/forgebuild/i18n"),
|
|
];
|
|
|
|
for entry in options {
|
|
if entry.is_dir() {
|
|
// We found a matching entry
|
|
return Some(entry);
|
|
}
|
|
}
|
|
|
|
// Maybe spec folder exists but hasn't been cloned
|
|
if bindir.join("../../spec/").is_dir() {
|
|
// TODO: Try to clone
|
|
unimplemented!("TODO: The spec submodule has not been cloned. We should clone it here. In the meantime, you can do it manually from the build.rs repo using git submodule init && git submodule update");
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
fn load_translations_from_file(path: &Path) -> HashMap<String, String> {
|
|
match fs::read_to_string(&path) {
|
|
Ok(content) => {
|
|
//let trans: HashMap<String, String> = 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<String, String> {
|
|
match &*LANG {
|
|
Lang::File(path) => {
|
|
load_translations_from_file(&path)
|
|
},
|
|
Lang::Some(lang) => {
|
|
if let Some(i18ndir) = find_translations() {
|
|
let i18nfile = i18ndir.join(format!("{}.json", lang));
|
|
if !i18nfile.is_file() {
|
|
panic!("No such translation file: {:?}", i18nfile);
|
|
}
|
|
return load_translations_from_file(&i18nfile)
|
|
} else {
|
|
panic!("No translation folder found.");
|
|
}
|
|
},
|
|
_ => {
|
|
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));
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn info(msg: &str, vars: &Context) {
|
|
if LOGLEVEL.info {
|
|
println!("{}{}", trans("info"), trans_context(msg, vars));
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn error(msg: &str, vars: &Context) {
|
|
if LOGLEVEL.error {
|
|
eprintln!("{}{}", trans("error"), trans_context(msg, vars));
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn warn(msg: &str, vars: &Context) {
|
|
if LOGLEVEL.error {
|
|
eprintln!("{}{}", trans("warning"), trans_context(msg, vars));
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn debug(msg: &str, vars: &Context) {
|
|
if LOGLEVEL.debug {
|
|
println!("{}{}", trans("debug"), trans_context(msg, vars));
|
|
}
|
|
}
|