2020-04-30 16:53:49 +00:00
use std ::collections ::HashMap ;
2020-11-25 22:59:39 +00:00
use std ::env ;
2020-04-30 16:53:49 +00:00
use std ::fs ;
2020-12-01 17:19:53 +00:00
use std ::path ::{ Path , PathBuf } ;
2021-01-04 15:25:47 +00:00
// 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 ;
2020-04-30 16:53:49 +00:00
use lazy_static ::lazy_static ;
2020-11-25 22:59:39 +00:00
lazy_static! {
2020-04-30 16:53:49 +00:00
static ref LOGLEVEL : LogLevel = LogLevel ::from_env ( ) ;
2020-11-28 15:48:23 +00:00
static ref LANG : Lang = Lang ::from_env ( ) ;
2020-04-30 16:53:49 +00:00
static ref TRANSLATIONS : HashMap < String , String > = load_translations ( ) ;
}
2020-11-27 18:37:46 +00:00
/// 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
2020-11-28 19:36:11 +00:00
/// JsonContext: debug output for Context as JSON
2020-12-01 17:19:53 +00:00
/// File(PathBuf): fixed translation file (instead of language code)
2020-11-27 18:37:46 +00:00
pub enum Lang {
None ,
2020-11-28 19:36:11 +00:00
JsonContext ,
2020-12-01 17:19:53 +00:00
Some ( String ) ,
File ( PathBuf ) ,
2020-11-27 18:37:46 +00:00
}
2020-11-28 15:48:23 +00:00
impl Lang {
fn from_env ( ) -> Lang {
let lang =
env ::var ( " LANG " ) . expect ( " $LANG not set in environment. Your machine is misconfigured! " ) ;
2020-12-01 17:19:53 +00:00
match lang . to_uppercase ( ) . as_str ( ) {
2020-11-28 15:48:23 +00:00
" NONE " = > Lang ::None ,
2020-11-28 19:36:11 +00:00
" JSON " = > Lang ::JsonContext ,
2021-01-04 14:34:09 +00:00
" C " = > {
// Special case: when no lang is specified, default to english
Lang ::Some ( " en " . to_string ( ) )
} ,
2020-12-01 17:19:53 +00:00
_ = > {
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 ( ) )
}
}
2020-11-28 15:48:23 +00:00
}
}
}
pub type Context = HashMap < String , String > ;
2020-04-30 23:45:48 +00:00
2022-01-05 17:38:54 +00:00
/// 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 ( ) ;
2021-01-04 15:25:47 +00:00
2022-01-05 17:38:54 +00:00
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 ) ;
2020-11-23 18:45:35 +00:00
}
}
2022-01-05 17:38:54 +00:00
// 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 ;
2020-11-23 18:45:35 +00:00
}
2020-12-01 17:19:53 +00:00
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 ) ;
}
}
}
2020-04-30 16:53:49 +00:00
fn load_translations ( ) -> HashMap < String , String > {
2020-11-27 18:37:46 +00:00
match & * LANG {
2020-12-01 17:19:53 +00:00
Lang ::File ( path ) = > {
load_translations_from_file ( & path )
} ,
2020-11-27 18:37:46 +00:00
Lang ::Some ( lang ) = > {
2022-01-05 17:38:54 +00:00
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. " ) ;
2020-11-27 18:37:46 +00:00
}
} ,
_ = > {
HashMap ::new ( )
2020-04-30 16:53:49 +00:00
}
}
}
fn trans ( key : & str ) -> String {
2020-11-27 18:37:46 +00:00
match & * LANG {
2020-12-01 17:19:53 +00:00
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 ( ) )
}
} ,
2020-11-28 19:36:11 +00:00
Lang ::Some ( lang ) = > {
if let Some ( t ) = TRANSLATIONS . get ( key ) {
t . to_string ( )
} else {
panic! ( " Missing translation for {} in lang {} " , key , lang )
2020-11-27 18:37:46 +00:00
}
} ,
2020-11-28 19:36:11 +00:00
Lang ::JsonContext = > String ::new ( ) ,
Lang ::None = > key . to_string ( ) ,
2020-04-30 16:53:49 +00:00
}
}
2020-11-28 19:36:11 +00:00
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 )
}
}
2020-04-30 16:53:49 +00:00
struct LogLevel {
info : bool ,
debug : bool ,
2020-11-25 22:59:39 +00:00
error : bool ,
2020-04-30 16:53:49 +00:00
}
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 ( ) {
2020-11-25 22:59:39 +00:00
" info " = > { }
" debug " = > {
debug = true ;
}
" error " = > {
info = false ;
}
2020-04-30 16:53:49 +00:00
_ = > {
// This happens before loglevel initialization
// so we can't use warn function
2020-11-25 22:59:39 +00:00
eprintln! (
" $LOG level is incorrect: {} (can be: debug, info, error " ,
env_log
) ;
2020-04-30 16:53:49 +00:00
}
}
2020-11-25 22:59:39 +00:00
return LogLevel { info , debug , error } ;
2020-04-30 16:53:49 +00:00
}
}
2020-11-28 15:48:23 +00:00
/// 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 ( ) ;
2020-04-30 16:53:49 +00:00
}
2020-11-28 15:48:23 +00:00
return vars
. iter ( )
. fold ( msg . to_string ( ) , | prev , ( key , val ) | prev . replace ( key , val ) ) ;
2020-04-30 16:53:49 +00:00
}
2022-01-06 17:12:54 +00:00
#[ allow(dead_code) ]
2020-11-28 15:48:23 +00:00
pub fn info ( msg : & str , vars : & Context ) {
2020-04-30 16:53:49 +00:00
if LOGLEVEL . info {
2020-12-01 17:19:53 +00:00
println! ( " {} {} " , trans ( " info " ) , trans_context ( msg , vars ) ) ;
2020-04-30 16:53:49 +00:00
}
}
2022-01-06 17:12:54 +00:00
#[ allow(dead_code) ]
2020-11-28 15:48:23 +00:00
pub fn error ( msg : & str , vars : & Context ) {
2020-04-30 16:53:49 +00:00
if LOGLEVEL . error {
2020-11-28 19:36:11 +00:00
eprintln! ( " {} {} " , trans ( " error " ) , trans_context ( msg , vars ) ) ;
2020-04-30 16:53:49 +00:00
}
}
2022-01-06 17:12:54 +00:00
#[ allow(dead_code) ]
2020-11-28 15:48:23 +00:00
pub fn warn ( msg : & str , vars : & Context ) {
2020-04-30 16:53:49 +00:00
if LOGLEVEL . error {
2020-11-28 19:36:11 +00:00
eprintln! ( " {} {} " , trans ( " warning " ) , trans_context ( msg , vars ) ) ;
2020-04-30 16:53:49 +00:00
}
}
2022-01-06 17:12:54 +00:00
#[ allow(dead_code) ]
2020-11-28 15:48:23 +00:00
pub fn debug ( msg : & str , vars : & Context ) {
2020-04-30 16:53:49 +00:00
if LOGLEVEL . debug {
2020-11-28 19:36:11 +00:00
println! ( " {} {} " , trans ( " debug " ) , trans_context ( msg , vars ) ) ;
2020-04-30 16:53:49 +00:00
}
}