From f35808cb89b98400086d17804b8b53749c4d4739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 1 Sep 2023 09:18:55 +0300 Subject: [PATCH] Make $nu constant (#10160) --- Cargo.lock | 4 +- crates/nu-cli/src/repl.rs | 7 +- .../tests/support/completions_helpers.rs | 8 +- crates/nu-engine/Cargo.toml | 2 - crates/nu-engine/src/eval.rs | 189 +----------------- crates/nu-protocol/Cargo.toml | 2 + crates/nu-protocol/src/engine/engine_state.rs | 9 + crates/nu-protocol/src/eval_const.rs | 179 ++++++++++++++++- crates/nu-system/Cargo.toml | 57 +++--- crates/nu-system/src/lib.rs | 1 + crates/nu-system/src/os_info.rs | 21 ++ src/main.rs | 10 +- src/run.rs | 13 +- 13 files changed, 284 insertions(+), 218 deletions(-) create mode 100644 crates/nu-system/src/os_info.rs diff --git a/Cargo.lock b/Cargo.lock index 18e898e12b..01029fcbdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2836,7 +2836,6 @@ dependencies = [ "nu-path", "nu-protocol", "nu-utils", - "sysinfo", ] [[package]] @@ -2932,6 +2931,8 @@ dependencies = [ "indexmap 2.0.0", "lru", "miette", + "nu-path", + "nu-system", "nu-test-support", "nu-utils", "num-format", @@ -2967,6 +2968,7 @@ dependencies = [ "ntapi", "once_cell", "procfs", + "sysinfo", "winapi", ] diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 2cac47816d..e77218f63b 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -16,8 +16,9 @@ use nu_parser::{lex, parse, trim_quotes_str}; use nu_protocol::{ config::NuCursorShape, engine::{EngineState, Stack, StateWorkingSet}, + eval_const::create_nu_constant, report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, - Value, + Value, NU_VARIABLE_ID, }; use nu_utils::utils::perf; use reedline::{ @@ -164,6 +165,10 @@ pub fn evaluate_repl( engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64); + // Regenerate the $nu constant to contain the startup time and any other potential updates + let nu_const = create_nu_constant(engine_state, Span::unknown())?; + engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const); + if load_std_lib.is_none() && engine_state.get_config().show_banner { eval_source( engine_state, diff --git a/crates/nu-cli/tests/support/completions_helpers.rs b/crates/nu-cli/tests/support/completions_helpers.rs index 41932cdb42..d1bc9056eb 100644 --- a/crates/nu-cli/tests/support/completions_helpers.rs +++ b/crates/nu-cli/tests/support/completions_helpers.rs @@ -4,7 +4,8 @@ use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, - PipelineData, ShellError, Span, Value, + eval_const::create_nu_constant, + PipelineData, ShellError, Span, Value, NU_VARIABLE_ID, }; use nu_test_support::fs; use reedline::Suggestion; @@ -28,6 +29,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) { // Create a new engine with default context let mut engine_state = create_default_context(); + // Add $nu + let nu_const = + create_nu_constant(&engine_state, Span::test_data()).expect("Failed creating $nu"); + engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const); + // New stack let mut stack = Stack::new(); diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 229ba05801..d15c93f2ac 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -16,7 +16,5 @@ nu-path = { path = "../nu-path", version = "0.84.1" } nu-glob = { path = "../nu-glob", version = "0.84.1" } nu-utils = { path = "../nu-utils", version = "0.84.1" } -sysinfo = "0.29" - [features] plugin = [] diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index bf2e4919cd..d7e431016d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -10,9 +10,8 @@ use nu_protocol::{ PipelineMetadata, Range, Record, ShellError, Span, Spanned, Unit, Value, VarId, ENV_VARIABLE_ID, }; +use std::collections::HashMap; use std::time::Instant; -use std::{collections::HashMap, path::PathBuf}; -use sysinfo::SystemExt; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -1226,183 +1225,6 @@ pub fn eval_subexpression( Ok(input) } -pub fn eval_nu_variable(engine_state: &EngineState, span: Span) -> Result { - fn canonicalize_path(engine_state: &EngineState, path: &PathBuf) -> PathBuf { - let cwd = engine_state.current_work_dir(); - - if path.exists() { - match nu_path::canonicalize_with(path, cwd) { - Ok(canon_path) => canon_path, - Err(_) => path.clone(), - } - } else { - path.clone() - } - } - - let mut record = Record::new(); - - record.push( - "default-config-dir", - if let Some(mut path) = nu_path::config_dir() { - path.push("nushell"); - Value::string(path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not get config directory".into()), - span, - ) - }, - ); - - record.push( - "config-path", - if let Some(path) = engine_state.get_config_path("config-path") { - let canon_config_path = canonicalize_path(engine_state, path); - Value::string(canon_config_path.to_string_lossy(), span) - } else if let Some(mut path) = nu_path::config_dir() { - path.push("nushell"); - path.push("config.nu"); - Value::string(path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not get config directory".into()), - span, - ) - }, - ); - - record.push( - "env-path", - if let Some(path) = engine_state.get_config_path("env-path") { - let canon_env_path = canonicalize_path(engine_state, path); - Value::string(canon_env_path.to_string_lossy(), span) - } else if let Some(mut path) = nu_path::config_dir() { - path.push("nushell"); - path.push("env.nu"); - Value::string(path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not find environment path".into()), - span, - ) - }, - ); - - record.push( - "history-path", - if let Some(mut path) = nu_path::config_dir() { - path.push("nushell"); - match engine_state.config.history_file_format { - nu_protocol::HistoryFileFormat::Sqlite => { - path.push("history.sqlite3"); - } - nu_protocol::HistoryFileFormat::PlainText => { - path.push("history.txt"); - } - } - let canon_hist_path = canonicalize_path(engine_state, &path); - Value::string(canon_hist_path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not find history path".into()), - span, - ) - }, - ); - - record.push( - "loginshell-path", - if let Some(mut path) = nu_path::config_dir() { - path.push("nushell"); - path.push("login.nu"); - let canon_login_path = canonicalize_path(engine_state, &path); - Value::string(canon_login_path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not find login shell path".into()), - span, - ) - }, - ); - - #[cfg(feature = "plugin")] - { - record.push( - "plugin-path", - if let Some(path) = &engine_state.plugin_signatures { - let canon_plugin_path = canonicalize_path(engine_state, path); - Value::string(canon_plugin_path.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not get plugin signature location".into()), - span, - ) - }, - ); - } - - record.push( - "home-path", - if let Some(path) = nu_path::home_dir() { - let canon_home_path = canonicalize_path(engine_state, &path); - Value::string(canon_home_path.to_string_lossy(), span) - } else { - Value::error(ShellError::IOError("Could not get home path".into()), span) - }, - ); - - record.push("temp-path", { - let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); - Value::string(canon_temp_path.to_string_lossy(), span) - }); - - record.push("pid", Value::int(std::process::id().into(), span)); - - record.push("os-info", { - let sys = sysinfo::System::new(); - let ver = match sys.kernel_version() { - Some(v) => v, - None => "unknown".into(), - }; - Value::record( - record! { - "name" => Value::string(std::env::consts::OS, span), - "arch" => Value::string(std::env::consts::ARCH, span), - "family" => Value::string(std::env::consts::FAMILY, span), - "kernel_version" => Value::string(ver, span), - }, - span, - ) - }); - - record.push( - "startup-time", - Value::duration(engine_state.get_startup_time(), span), - ); - - record.push( - "is-interactive", - Value::bool(engine_state.is_interactive, span), - ); - - record.push("is-login", Value::bool(engine_state.is_login, span)); - - record.push( - "current-exe", - if let Ok(current_exe) = std::env::current_exe() { - Value::string(current_exe.to_string_lossy(), span) - } else { - Value::error( - ShellError::IOError("Could not get current executable path".to_string()), - span, - ) - }, - ); - - Ok(Value::record(record, span)) -} - pub fn eval_variable( engine_state: &EngineState, stack: &Stack, @@ -1411,7 +1233,14 @@ pub fn eval_variable( ) -> Result { match var_id { // $nu - nu_protocol::NU_VARIABLE_ID => eval_nu_variable(engine_state, span), + nu_protocol::NU_VARIABLE_ID => { + if let Some(val) = engine_state.get_constant(var_id) { + Ok(val.clone()) + } else { + Err(ShellError::VariableNotFoundAtRuntime { span }) + } + } + // $env ENV_VARIABLE_ID => { let env_vars = stack.get_env_vars(engine_state); let env_columns = env_vars.keys(); diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 89d529e03c..914ffc162a 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -14,6 +14,8 @@ bench = false [dependencies] nu-utils = { path = "../nu-utils", version = "0.84.1" } +nu-path = { path = "../nu-path", version = "0.84.1" } +nu-system = { path = "../nu-system", version = "0.84.1" } byte-unit = "4.0" chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 385bd32d00..d92bfcb0a9 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -748,6 +748,15 @@ impl EngineState { .expect("internal error: missing variable") } + pub fn get_constant(&self, var_id: VarId) -> Option<&Value> { + let var = self.get_var(var_id); + var.const_val.as_ref() + } + + pub fn set_variable_const_val(&mut self, var_id: VarId, val: Value) { + self.vars[var_id].const_val = Some(val); + } + #[allow(clippy::borrowed_box)] pub fn get_decl(&self, decl_id: DeclId) -> &Box { self.decls diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 5d5351f0a7..01bb5a98cc 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -1,8 +1,183 @@ use crate::{ ast::{Block, Call, Expr, Expression, PipelineElement}, - engine::StateWorkingSet, - PipelineData, Record, ShellError, Span, Value, + engine::{EngineState, StateWorkingSet}, + record, HistoryFileFormat, PipelineData, Record, ShellError, Span, Value, }; +use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name}; +use std::path::PathBuf; + +pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result { + fn canonicalize_path(engine_state: &EngineState, path: &PathBuf) -> PathBuf { + let cwd = engine_state.current_work_dir(); + + if path.exists() { + match nu_path::canonicalize_with(path, cwd) { + Ok(canon_path) => canon_path, + Err(_) => path.clone(), + } + } else { + path.clone() + } + } + + let mut record = Record::new(); + + record.push( + "default-config-dir", + if let Some(mut path) = nu_path::config_dir() { + path.push("nushell"); + Value::string(path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not get config directory".into()), + span, + ) + }, + ); + + record.push( + "config-path", + if let Some(path) = engine_state.get_config_path("config-path") { + let canon_config_path = canonicalize_path(engine_state, path); + Value::string(canon_config_path.to_string_lossy(), span) + } else if let Some(mut path) = nu_path::config_dir() { + path.push("nushell"); + path.push("config.nu"); + Value::string(path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not get config directory".into()), + span, + ) + }, + ); + + record.push( + "env-path", + if let Some(path) = engine_state.get_config_path("env-path") { + let canon_env_path = canonicalize_path(engine_state, path); + Value::string(canon_env_path.to_string_lossy(), span) + } else if let Some(mut path) = nu_path::config_dir() { + path.push("nushell"); + path.push("env.nu"); + Value::string(path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not find environment path".into()), + span, + ) + }, + ); + + record.push( + "history-path", + if let Some(mut path) = nu_path::config_dir() { + path.push("nushell"); + match engine_state.config.history_file_format { + HistoryFileFormat::Sqlite => { + path.push("history.sqlite3"); + } + HistoryFileFormat::PlainText => { + path.push("history.txt"); + } + } + let canon_hist_path = canonicalize_path(engine_state, &path); + Value::string(canon_hist_path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not find history path".into()), + span, + ) + }, + ); + + record.push( + "loginshell-path", + if let Some(mut path) = nu_path::config_dir() { + path.push("nushell"); + path.push("login.nu"); + let canon_login_path = canonicalize_path(engine_state, &path); + Value::string(canon_login_path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not find login shell path".into()), + span, + ) + }, + ); + + #[cfg(feature = "plugin")] + { + record.push( + "plugin-path", + if let Some(path) = &engine_state.plugin_signatures { + let canon_plugin_path = canonicalize_path(engine_state, path); + Value::string(canon_plugin_path.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not get plugin signature location".into()), + span, + ) + }, + ); + } + + record.push( + "home-path", + if let Some(path) = nu_path::home_dir() { + let canon_home_path = canonicalize_path(engine_state, &path); + Value::string(canon_home_path.to_string_lossy(), span) + } else { + Value::error(ShellError::IOError("Could not get home path".into()), span) + }, + ); + + record.push("temp-path", { + let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir()); + Value::string(canon_temp_path.to_string_lossy(), span) + }); + + record.push("pid", Value::int(std::process::id().into(), span)); + + record.push("os-info", { + let ver = get_kernel_version(); + Value::record( + record! { + "name" => Value::string(get_os_name(), span), + "arch" => Value::string(get_os_arch(), span), + "family" => Value::string(get_os_family(), span), + "kernel_version" => Value::string(ver, span), + }, + span, + ) + }); + + record.push( + "startup-time", + Value::duration(engine_state.get_startup_time(), span), + ); + + record.push( + "is-interactive", + Value::bool(engine_state.is_interactive, span), + ); + + record.push("is-login", Value::bool(engine_state.is_login, span)); + + record.push( + "current-exe", + if let Ok(current_exe) = std::env::current_exe() { + Value::string(current_exe.to_string_lossy(), span) + } else { + Value::error( + ShellError::IOError("Could not get current executable path".to_string()), + span, + ) + }, + ); + + Ok(Value::record(record, span)) +} fn eval_const_call( working_set: &StateWorkingSet, diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index fcf340aac7..8b922a7f40 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -15,6 +15,7 @@ bench = false [dependencies] libc = "0.2" log = "0.4" +sysinfo = "0.29" [target.'cfg(target_family = "unix")'.dependencies] nix = { version = "0.26", default-features = false, features = ["fs", "term", "process", "signal"] } @@ -32,31 +33,31 @@ ntapi = "0.4" once_cell = "1.18" winapi = { version = "0.3", features = [ "tlhelp32", - "fileapi", - "handleapi", - "ifdef", - "ioapiset", - "minwindef", - "pdh", - "psapi", - "synchapi", - "sysinfoapi", - "winbase", - "winerror", - "winioctl", - "winnt", - "oleauto", - "wbemcli", - "rpcdce", - "combaseapi", - "objidl", - "powerbase", - "netioapi", - "lmcons", - "lmaccess", - "lmapibuf", - "memoryapi", - "shellapi", - "std", - "securitybaseapi", -] } + "fileapi", + "handleapi", + "ifdef", + "ioapiset", + "minwindef", + "pdh", + "psapi", + "synchapi", + "sysinfoapi", + "winbase", + "winerror", + "winioctl", + "winnt", + "oleauto", + "wbemcli", + "rpcdce", + "combaseapi", + "objidl", + "powerbase", + "netioapi", + "lmcons", + "lmaccess", + "lmapibuf", + "memoryapi", + "shellapi", + "std", + "securitybaseapi", + ] } diff --git a/crates/nu-system/src/lib.rs b/crates/nu-system/src/lib.rs index 2283a94b78..7f04a6d270 100644 --- a/crates/nu-system/src/lib.rs +++ b/crates/nu-system/src/lib.rs @@ -3,6 +3,7 @@ mod foreground; mod linux; #[cfg(target_os = "macos")] mod macos; +pub mod os_info; #[cfg(target_os = "windows")] mod windows; diff --git a/crates/nu-system/src/os_info.rs b/crates/nu-system/src/os_info.rs new file mode 100644 index 0000000000..d0611284af --- /dev/null +++ b/crates/nu-system/src/os_info.rs @@ -0,0 +1,21 @@ +use sysinfo::SystemExt; + +pub fn get_os_name() -> &'static str { + std::env::consts::OS +} + +pub fn get_os_arch() -> &'static str { + std::env::consts::ARCH +} + +pub fn get_os_family() -> &'static str { + std::env::consts::FAMILY +} + +pub fn get_kernel_version() -> String { + let sys = sysinfo::System::new(); + match sys.kernel_version() { + Some(v) => v, + None => "unknown".to_string(), + } +} diff --git a/src/main.rs b/src/main.rs index 1efa9ac295..9b146ed036 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,8 +24,10 @@ use log::Level; use miette::Result; use nu_cli::gather_parent_env_vars; use nu_cmd_base::util::get_init_cwd; -use nu_protocol::{engine::EngineState, report_error_new, Value}; -use nu_protocol::{util::BufferedReader, PipelineData, RawStream}; +use nu_protocol::{ + engine::EngineState, eval_const::create_nu_constant, report_error_new, util::BufferedReader, + PipelineData, RawStream, Span, Value, NU_VARIABLE_ID, +}; use nu_std::load_standard_library; use nu_utils::utils::perf; use run::{run_commands, run_file, run_repl}; @@ -274,6 +276,10 @@ fn main() -> Result<()> { use_color, ); + // Set up the $nu constant before evaluating config files (need to have $nu available in them) + let nu_const = create_nu_constant(&engine_state, input.span().unwrap_or_else(Span::unknown))?; + engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const); + if let Some(commands) = parsed_nu_cli_args.commands.clone() { run_commands( &mut engine_state, diff --git a/src/run.rs b/src/run.rs index 7d2ff7e44f..6c6a5e1ce2 100644 --- a/src/run.rs +++ b/src/run.rs @@ -7,7 +7,8 @@ use crate::{ #[cfg(feature = "plugin")] use nu_cli::read_plugin_file; use nu_cli::{evaluate_commands, evaluate_file, evaluate_repl}; -use nu_protocol::PipelineData; +use nu_protocol::eval_const::create_nu_constant; +use nu_protocol::{PipelineData, Span, NU_VARIABLE_ID}; use nu_utils::utils::perf; pub(crate) fn run_commands( @@ -82,8 +83,14 @@ pub(crate) fn run_commands( use_color, ); } + // Before running commands, set up the startup time engine_state.set_startup_time(entire_start_time.elapsed().as_nanos() as i64); + + // Regenerate the $nu constant to contain the startup time and any other potential updates + let nu_const = create_nu_constant(engine_state, commands.span)?; + engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const); + let start_time = std::time::Instant::now(); let ret_val = evaluate_commands( commands, @@ -169,6 +176,10 @@ pub(crate) fn run_file( use_color, ); + // Regenerate the $nu constant to contain the startup time and any other potential updates + let nu_const = create_nu_constant(engine_state, input.span().unwrap_or_else(Span::unknown))?; + engine_state.set_variable_const_val(NU_VARIABLE_ID, nu_const); + let start_time = std::time::Instant::now(); let ret_val = evaluate_file( script_name,