2021-12-17 02:04:54 +01:00
use std ::collections ::HashMap ;
2022-01-04 23:30:34 +01:00
use std ::path ::{ Path , PathBuf } ;
2021-12-17 02:04:54 +01:00
2023-04-05 18:56:48 +02:00
use nu_protocol ::ast ::{ Call , Expr , PathMember } ;
2021-12-17 02:04:54 +01:00
use nu_protocol ::engine ::{ EngineState , Stack } ;
2023-04-05 18:56:48 +02:00
use nu_protocol ::{ PipelineData , ShellError , Span , Value , VarId } ;
2021-12-17 02:04:54 +01:00
2022-08-31 22:32:56 +02:00
use nu_path ::canonicalize_with ;
2021-12-17 02:04:54 +01:00
use crate ::eval_block ;
2022-03-03 09:38:31 +01:00
#[ cfg(windows) ]
const ENV_PATH_NAME : & str = " Path " ;
#[ cfg(windows) ]
const ENV_PATH_NAME_SECONDARY : & str = " PATH " ;
#[ cfg(not(windows)) ]
const ENV_PATH_NAME : & str = " PATH " ;
2022-02-20 15:27:59 +01:00
const ENV_CONVERSIONS : & str = " ENV_CONVERSIONS " ;
enum ConversionResult {
Ok ( Value ) ,
ConversionError ( ShellError ) , // Failure during the conversion itself
GeneralError ( ShellError ) , // Other error not directly connected to running the conversion
CellPathError , // Error looking up the ENV_VAR.to_/from_string fields in $env.ENV_CONVERSIONS
}
2021-12-17 02:04:54 +01:00
/// Translate environment variables from Strings to Values. Requires config to be already set up in
/// case the user defined custom env conversions in config.nu.
///
/// It returns Option instead of Result since we do want to translate all the values we can and
/// skip errors. This function is called in the main() so we want to keep running, we cannot just
/// exit.
2022-02-20 15:27:59 +01:00
pub fn convert_env_values ( engine_state : & mut EngineState , stack : & Stack ) -> Option < ShellError > {
2021-12-17 02:04:54 +01:00
let mut error = None ;
2022-01-04 23:30:34 +01:00
let mut new_scope = HashMap ::new ( ) ;
2021-12-17 02:04:54 +01:00
2022-05-07 21:39:22 +02:00
let env_vars = engine_state . render_env_vars ( ) ;
for ( name , val ) in env_vars {
2022-02-20 15:27:59 +01:00
match get_converted_value ( engine_state , stack , name , val , " from_string " ) {
ConversionResult ::Ok ( v ) = > {
let _ = new_scope . insert ( name . to_string ( ) , v ) ;
}
ConversionResult ::ConversionError ( e ) = > error = error . or ( Some ( e ) ) ,
ConversionResult ::GeneralError ( _ ) = > continue ,
ConversionResult ::CellPathError = > {
let _ = new_scope . insert ( name . to_string ( ) , val . clone ( ) ) ;
2021-12-17 02:04:54 +01:00
}
}
}
2022-03-11 23:18:39 +01:00
#[ cfg(not(windows)) ]
{
error = error . or_else ( | | ensure_path ( & mut new_scope , ENV_PATH_NAME ) ) ;
}
#[ cfg(windows) ]
{
let first_result = ensure_path ( & mut new_scope , ENV_PATH_NAME ) ;
if first_result . is_some ( ) {
let second_result = ensure_path ( & mut new_scope , ENV_PATH_NAME_SECONDARY ) ;
if second_result . is_some ( ) {
error = error . or ( first_result ) ;
}
}
}
2022-05-07 21:39:22 +02:00
if let Ok ( last_overlay_name ) = & stack . last_overlay_name ( ) {
if let Some ( env_vars ) = engine_state . env_vars . get_mut ( last_overlay_name ) {
for ( k , v ) in new_scope {
env_vars . insert ( k , v ) ;
}
} else {
error = error . or_else ( | | {
2023-03-06 18:33:09 +01:00
Some ( ShellError ::NushellFailedHelp { msg : " Last active overlay not found in permanent state. " . into ( ) , help : " This error happened during the conversion of environment variables from strings to Nushell values. " . into ( ) } )
2022-05-07 21:39:22 +02:00
} ) ;
}
} else {
error = error . or_else ( | | {
2023-03-06 18:33:09 +01:00
Some ( ShellError ::NushellFailedHelp { msg : " Last active overlay not found in stack. " . into ( ) , help : " This error happened during the conversion of environment variables from strings to Nushell values. " . into ( ) } )
2022-05-07 21:39:22 +02:00
} ) ;
2022-01-04 23:30:34 +01:00
}
2021-12-17 02:04:54 +01:00
error
}
/// Translate one environment variable from Value to String
2022-03-11 23:18:39 +01:00
///
/// Returns Ok(None) if the env var is not
2021-12-17 02:04:54 +01:00
pub fn env_to_string (
env_name : & str ,
2022-03-11 23:18:39 +01:00
value : & Value ,
2021-12-17 02:04:54 +01:00
engine_state : & EngineState ,
2022-01-04 23:30:34 +01:00
stack : & Stack ,
2021-12-17 02:04:54 +01:00
) -> Result < String , ShellError > {
2022-03-11 23:18:39 +01:00
match get_converted_value ( engine_state , stack , env_name , value , " to_string " ) {
2022-02-20 15:27:59 +01:00
ConversionResult ::Ok ( v ) = > Ok ( v . as_string ( ) ? ) ,
ConversionResult ::ConversionError ( e ) = > Err ( e ) ,
ConversionResult ::GeneralError ( e ) = > Err ( e ) ,
2022-03-11 23:18:39 +01:00
ConversionResult ::CellPathError = > match value . as_string ( ) {
Ok ( s ) = > Ok ( s ) ,
Err ( _ ) = > {
if env_name = = ENV_PATH_NAME {
// Try to convert PATH/Path list to a string
match value {
Value ::List { vals , .. } = > {
let paths = vals
. iter ( )
. map ( | v | v . as_string ( ) )
. collect ::< Result < Vec < _ > , _ > > ( ) ? ;
match std ::env ::join_paths ( paths ) {
Ok ( p ) = > Ok ( p . to_string_lossy ( ) . to_string ( ) ) ,
2023-03-06 18:33:09 +01:00
Err ( _ ) = > Err ( ShellError ::EnvVarNotAString {
envvar_name : env_name . to_string ( ) ,
span : value . span ( ) ? ,
} ) ,
2022-03-11 23:18:39 +01:00
}
}
2023-03-06 18:33:09 +01:00
_ = > Err ( ShellError ::EnvVarNotAString {
envvar_name : env_name . to_string ( ) ,
span : value . span ( ) ? ,
} ) ,
2022-03-11 23:18:39 +01:00
}
} else {
2023-03-06 18:33:09 +01:00
Err ( ShellError ::EnvVarNotAString {
envvar_name : env_name . to_string ( ) ,
span : value . span ( ) ? ,
} )
2022-03-11 23:18:39 +01:00
}
}
} ,
2021-12-17 02:04:54 +01:00
}
}
/// Translate all environment variables from Values to Strings
pub fn env_to_strings (
engine_state : & EngineState ,
2022-01-04 23:30:34 +01:00
stack : & Stack ,
2021-12-17 02:04:54 +01:00
) -> Result < HashMap < String , String > , ShellError > {
2022-01-04 23:30:34 +01:00
let env_vars = stack . get_env_vars ( engine_state ) ;
2021-12-17 02:04:54 +01:00
let mut env_vars_str = HashMap ::new ( ) ;
for ( env_name , val ) in env_vars {
2022-03-11 23:18:39 +01:00
match env_to_string ( & env_name , & val , engine_state , stack ) {
Ok ( val_str ) = > {
env_vars_str . insert ( env_name , val_str ) ;
}
2023-03-06 18:33:09 +01:00
Err ( ShellError ::EnvVarNotAString { .. } ) = > { } // ignore non-string values
2022-03-11 23:18:39 +01:00
Err ( e ) = > return Err ( e ) ,
}
2021-12-17 02:04:54 +01:00
}
Ok ( env_vars_str )
}
2022-01-04 23:30:34 +01:00
/// Shorthand for env_to_string() for PWD with custom error
pub fn current_dir_str ( engine_state : & EngineState , stack : & Stack ) -> Result < String , ShellError > {
if let Some ( pwd ) = stack . get_env_var ( engine_state , " PWD " ) {
2022-03-11 23:18:39 +01:00
match env_to_string ( " PWD " , & pwd , engine_state , stack ) {
2022-01-04 23:30:34 +01:00
Ok ( cwd ) = > {
if Path ::new ( & cwd ) . is_absolute ( ) {
Ok ( cwd )
} else {
2022-04-18 14:34:10 +02:00
Err ( ShellError ::GenericError (
2022-01-04 23:30:34 +01:00
" Invalid current directory " . to_string ( ) ,
2023-01-30 02:37:54 +01:00
format! ( " The 'PWD' environment variable must be set to an absolute path. Found: ' {cwd} ' " ) ,
2022-04-18 14:34:10 +02:00
Some ( pwd . span ( ) ? ) ,
None ,
Vec ::new ( )
2022-01-04 23:30:34 +01:00
) )
}
}
Err ( e ) = > Err ( e ) ,
}
} else {
2022-04-18 14:34:10 +02:00
Err ( ShellError ::GenericError (
2022-01-04 23:30:34 +01:00
" Current directory not found " . to_string ( ) ,
2022-04-18 14:34:10 +02:00
" " . to_string ( ) ,
None ,
Some ( " The environment variable 'PWD' was not found. It is required to define the current directory. " . to_string ( ) ) ,
Vec ::new ( ) ,
2022-01-04 23:30:34 +01:00
) )
}
}
/// Calls current_dir_str() and returns the current directory as a PathBuf
pub fn current_dir ( engine_state : & EngineState , stack : & Stack ) -> Result < PathBuf , ShellError > {
current_dir_str ( engine_state , stack ) . map ( PathBuf ::from )
}
2022-02-20 15:27:59 +01:00
2022-03-03 09:38:31 +01:00
/// Get the contents of path environment variable as a list of strings
///
/// On non-Windows: It will fetch PATH
/// On Windows: It will try to fetch Path first but if not present, try PATH
pub fn path_str (
engine_state : & EngineState ,
stack : & Stack ,
span : Span ,
) -> Result < String , ShellError > {
let ( pathname , pathval ) = match stack . get_env_var ( engine_state , ENV_PATH_NAME ) {
Some ( v ) = > Ok ( ( ENV_PATH_NAME , v ) ) ,
None = > {
#[ cfg(windows) ]
match stack . get_env_var ( engine_state , ENV_PATH_NAME_SECONDARY ) {
Some ( v ) = > Ok ( ( ENV_PATH_NAME_SECONDARY , v ) ) ,
2023-03-06 18:33:09 +01:00
None = > Err ( ShellError ::EnvVarNotFoundAtRuntime {
envvar_name : ENV_PATH_NAME_SECONDARY . to_string ( ) ,
2022-03-03 09:38:31 +01:00
span ,
2023-03-06 18:33:09 +01:00
} ) ,
2022-03-03 09:38:31 +01:00
}
#[ cfg(not(windows)) ]
2023-03-06 18:33:09 +01:00
Err ( ShellError ::EnvVarNotFoundAtRuntime {
envvar_name : ENV_PATH_NAME . to_string ( ) ,
2022-03-03 09:38:31 +01:00
span ,
2023-03-06 18:33:09 +01:00
} )
2022-03-03 09:38:31 +01:00
}
} ? ;
2022-03-11 23:18:39 +01:00
env_to_string ( pathname , & pathval , engine_state , stack )
2022-03-03 09:38:31 +01:00
}
2023-04-05 18:56:48 +02:00
pub const DIR_VAR_PARSER_INFO : & str = " dirs_var " ;
pub fn get_dirs_var_from_call ( call : & Call ) -> Option < VarId > {
call . get_parser_info ( DIR_VAR_PARSER_INFO ) . and_then ( | x | {
if let Expr ::Var ( id ) = x . expr {
Some ( id )
} else {
None
}
} )
}
2022-08-31 22:32:56 +02:00
/// This helper function is used to find files during eval
///
/// First, the actual current working directory is selected as
/// a) the directory of a file currently being parsed
/// b) current working directory (PWD)
///
/// Then, if the file is not found in the actual cwd, NU_LIB_DIRS is checked.
/// If there is a relative path in NU_LIB_DIRS, it is assumed to be relative to the actual cwd
/// determined in the first step.
///
/// Always returns an absolute path
pub fn find_in_dirs_env (
filename : & str ,
engine_state : & EngineState ,
stack : & Stack ,
2023-04-05 18:56:48 +02:00
dirs_var : Option < VarId > ,
2022-08-31 22:32:56 +02:00
) -> Result < Option < PathBuf > , ShellError > {
// Choose whether to use file-relative or PWD-relative path
let cwd = if let Some ( pwd ) = stack . get_env_var ( engine_state , " FILE_PWD " ) {
match env_to_string ( " FILE_PWD " , & pwd , engine_state , stack ) {
Ok ( cwd ) = > {
if Path ::new ( & cwd ) . is_absolute ( ) {
cwd
} else {
return Err ( ShellError ::GenericError (
" Invalid current directory " . to_string ( ) ,
2023-01-30 02:37:54 +01:00
format! ( " The 'FILE_PWD' environment variable must be set to an absolute path. Found: ' {cwd} ' " ) ,
2022-08-31 22:32:56 +02:00
Some ( pwd . span ( ) ? ) ,
None ,
Vec ::new ( )
) ) ;
}
}
Err ( e ) = > return Err ( e ) ,
}
} else {
current_dir_str ( engine_state , stack ) ?
} ;
2023-04-05 18:56:48 +02:00
let check_dir = | lib_dirs : Option < Value > | -> Option < PathBuf > {
if let Ok ( p ) = canonicalize_with ( filename , & cwd ) {
return Some ( p ) ;
}
2022-08-31 22:32:56 +02:00
let path = Path ::new ( filename ) ;
2023-04-05 18:56:48 +02:00
if ! path . is_relative ( ) {
return None ;
}
2022-08-31 22:32:56 +02:00
2023-04-05 18:56:48 +02:00
lib_dirs ?
. as_list ( )
. ok ( ) ?
. iter ( )
. map ( | lib_dir | -> Option < PathBuf > {
let dir = lib_dir . as_path ( ) . ok ( ) ? ;
let dir_abs = canonicalize_with ( dir , & cwd ) . ok ( ) ? ;
canonicalize_with ( filename , dir_abs ) . ok ( )
} )
. find ( Option ::is_some )
. flatten ( )
} ;
2022-08-31 22:32:56 +02:00
2023-04-05 18:56:48 +02:00
let lib_dirs = dirs_var
. and_then ( | dirs_var | engine_state . find_constant ( dirs_var , & [ ] ) )
. cloned ( ) ;
// TODO: remove (see #8310)
let lib_dirs_fallback = stack . get_env_var ( engine_state , " NU_LIB_DIRS " ) ;
Ok ( check_dir ( lib_dirs ) . or_else ( | | check_dir ( lib_dirs_fallback ) ) )
2022-08-31 22:32:56 +02:00
}
2022-02-20 15:27:59 +01:00
fn get_converted_value (
engine_state : & EngineState ,
stack : & Stack ,
name : & str ,
orig_val : & Value ,
direction : & str ,
) -> ConversionResult {
if let Some ( env_conversions ) = stack . get_env_var ( engine_state , ENV_CONVERSIONS ) {
let env_span = match env_conversions . span ( ) {
Ok ( span ) = > span ,
Err ( e ) = > {
return ConversionResult ::GeneralError ( e ) ;
}
} ;
let val_span = match orig_val . span ( ) {
Ok ( span ) = > span ,
Err ( e ) = > {
return ConversionResult ::GeneralError ( e ) ;
}
} ;
let path_members = & [
PathMember ::String {
val : name . to_string ( ) ,
span : env_span ,
2023-03-16 04:50:58 +01:00
optional : false ,
2022-02-20 15:27:59 +01:00
} ,
PathMember ::String {
val : direction . to_string ( ) ,
span : env_span ,
2023-03-16 04:50:58 +01:00
optional : false ,
2022-02-20 15:27:59 +01:00
} ,
] ;
2022-11-11 01:13:07 +01:00
if let Ok ( Value ::Closure {
2022-02-20 15:27:59 +01:00
val : block_id ,
span : from_span ,
..
2022-09-11 18:58:19 +02:00
} ) = env_conversions . follow_cell_path_not_from_user_input ( path_members , false )
2022-02-20 15:27:59 +01:00
{
let block = engine_state . get_block ( block_id ) ;
if let Some ( var ) = block . signature . get_positional ( 0 ) {
let mut stack = stack . gather_captures ( & block . captures ) ;
if let Some ( var_id ) = & var . var_id {
stack . add_var ( * var_id , orig_val . clone ( ) ) ;
}
2022-02-21 23:22:21 +01:00
let result = eval_block (
engine_state ,
& mut stack ,
block ,
2022-12-07 19:31:57 +01:00
PipelineData ::new_with_metadata ( None , val_span ) ,
2022-02-21 23:22:21 +01:00
true ,
true ,
) ;
2022-02-20 15:27:59 +01:00
match result {
Ok ( data ) = > ConversionResult ::Ok ( data . into_value ( val_span ) ) ,
Err ( e ) = > ConversionResult ::ConversionError ( e ) ,
}
} else {
2023-03-06 11:31:07 +01:00
ConversionResult ::ConversionError ( ShellError ::MissingParameter {
param_name : " block input " . into ( ) ,
span : from_span ,
} )
2022-02-20 15:27:59 +01:00
}
} else {
ConversionResult ::CellPathError
}
} else {
ConversionResult ::CellPathError
}
}
2022-03-11 23:18:39 +01:00
fn ensure_path ( scope : & mut HashMap < String , Value > , env_path_name : & str ) -> Option < ShellError > {
let mut error = None ;
// If PATH/Path is still a string, force-convert it to a list
match scope . get ( env_path_name ) {
Some ( Value ::String { val , span } ) = > {
// Force-split path into a list
let span = * span ;
let paths = std ::env ::split_paths ( val )
. map ( | p | Value ::String {
val : p . to_string_lossy ( ) . to_string ( ) ,
span ,
} )
. collect ( ) ;
scope . insert ( env_path_name . to_string ( ) , Value ::List { vals : paths , span } ) ;
}
Some ( Value ::List { vals , span } ) = > {
// Must be a list of strings
if ! vals . iter ( ) . all ( | v | matches! ( v , Value ::String { .. } ) ) {
error = error . or_else ( | | {
2022-04-18 14:34:10 +02:00
Some ( ShellError ::GenericError (
2023-01-30 02:37:54 +01:00
format! ( " Wrong {env_path_name} environment variable value " ) ,
format! ( " {env_path_name} must be a list of strings " ) ,
2022-04-18 14:34:10 +02:00
Some ( * span ) ,
None ,
Vec ::new ( ) ,
2022-03-11 23:18:39 +01:00
) )
} ) ;
}
}
Some ( val ) = > {
// All other values are errors
let span = match val . span ( ) {
Ok ( sp ) = > sp ,
Err ( e ) = > {
error = error . or ( Some ( e ) ) ;
2022-12-24 14:41:57 +01:00
Span ::unknown ( ) // FIXME: any better span to use here?
2022-03-11 23:18:39 +01:00
}
} ;
error = error . or_else ( | | {
2022-04-18 14:34:10 +02:00
Some ( ShellError ::GenericError (
2023-01-30 02:37:54 +01:00
format! ( " Wrong {env_path_name} environment variable value " ) ,
format! ( " {env_path_name} must be a list of strings " ) ,
2022-04-18 14:34:10 +02:00
Some ( span ) ,
None ,
Vec ::new ( ) ,
2022-03-11 23:18:39 +01:00
) )
} ) ;
}
None = > { /* not preset, do nothing */ }
}
error
}