diff --git a/Cargo.lock b/Cargo.lock index 157e02b338..ce2b828de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2561,6 +2561,7 @@ dependencies = [ "nix", "nu-ansi-term", "nu-cli", + "nu-cmd-lang", "nu-color-config", "nu-command", "nu-engine", @@ -2627,6 +2628,43 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nu-cmd-lang" +version = "0.76.1" +dependencies = [ + "atty", + "chrono", + "crossterm 0.24.0", + "fancy-regex", + "itertools", + "libc", + "log", + "lscolors", + "nu-ansi-term", + "nu-color-config", + "nu-engine", + "nu-explore", + "nu-json", + "nu-parser", + "nu-path", + "nu-pretty-hex", + "nu-protocol", + "nu-system", + "nu-table", + "nu-term-grid", + "nu-test-support", + "nu-utils", + "once_cell", + "pathdiff", + "rayon", + "serde", + "shadow-rs", + "sysinfo 0.27.7", + "tabled", + "terminal_size 0.2.1", + "url", +] + [[package]] name = "nu-color-config" version = "0.76.1" @@ -2680,6 +2718,7 @@ dependencies = [ "mime_guess", "notify", "nu-ansi-term", + "nu-cmd-lang", "nu-color-config", "nu-engine", "nu-explore", @@ -5008,6 +5047,7 @@ dependencies = [ "libc", "ntapi", "once_cell", + "rayon", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index 069b8ca7a3..6af1938229 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "crates/nu-engine", "crates/nu-parser", "crates/nu-system", + "crates/nu-cmd-lang", "crates/nu-command", "crates/nu-protocol", "crates/nu-plugin", @@ -48,6 +49,7 @@ miette = { version = "5.5.0", features = ["fancy-no-backtrace"] } nu-ansi-term = "0.46.0" nu-cli = { path = "./crates/nu-cli", version = "0.76.1" } nu-color-config = { path = "./crates/nu-color-config", version = "0.76.1" } +nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.76.1" } nu-command = { path = "./crates/nu-command", version = "0.76.1" } nu-engine = { path = "./crates/nu-engine", version = "0.76.1" } nu-json = { path = "./crates/nu-json", version = "0.76.1" } diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml new file mode 100644 index 0000000000..742953a7de --- /dev/null +++ b/crates/nu-cmd-lang/Cargo.toml @@ -0,0 +1,55 @@ +[package] +authors = ["The Nushell Project Developers"] +build = "build.rs" +description = "Nushell's core language commands" +repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang" +edition = "2021" +license = "MIT" +name = "nu-cmd-lang" +version = "0.76.1" + +[lib] +bench = false + +[dependencies] +serde = { version="1.0.123", features=["derive"] } +# used only for text_style Alignments +tabled = { version = "0.10.0", features = ["color"], default-features = false } + +nu-ansi-term = "0.46.0" +nu-color-config = { path = "../nu-color-config", version = "0.76.1" } +nu-engine = { path = "../nu-engine", version = "0.76.1" } +nu-explore = { path = "../nu-explore", version = "0.76.1" } +nu-json = { path="../nu-json", version = "0.76.1" } +nu-parser = { path = "../nu-parser", version = "0.76.1" } +nu-path = { path = "../nu-path", version = "0.76.1" } +nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.76.1" } +nu-protocol = { path = "../nu-protocol", version = "0.76.1" } +nu-system = { path = "../nu-system", version = "0.76.1" } +nu-table = { path = "../nu-table", version = "0.76.1" } +nu-term-grid = { path = "../nu-term-grid", version = "0.76.1" } +nu-utils = { path = "../nu-utils", version = "0.76.1" } + +atty = "0.2.14" +chrono = { version = "0.4.23", features = ["std", "unstable-locales"], default-features = false } +crossterm = "0.24.0" +fancy-regex = "0.11.0" +itertools = "0.10.0" +log = "0.4.14" +lscolors = { version = "0.12.0", features = ["crossterm"], default-features = false } +once_cell = "1.17" +pathdiff = "0.2.1" +rayon = "1.6.1" +shadow-rs = { version = "0.20.0", default-features = false } +sysinfo = "0.27.7" +terminal_size = "0.2.1" +url = "2.2.1" + +[target.'cfg(unix)'.dependencies] +libc = "0.2" + +[build-dependencies] +shadow-rs = { version = "0.20.0", default-features = false } + +[dev-dependencies] +nu-test-support = { path="../nu-test-support", version = "0.76.1" } diff --git a/crates/nu-cmd-lang/LICENSE b/crates/nu-cmd-lang/LICENSE new file mode 100644 index 0000000000..08090de572 --- /dev/null +++ b/crates/nu-cmd-lang/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 - 2022 The Nushell Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/nu-cmd-lang/build.rs b/crates/nu-cmd-lang/build.rs new file mode 100644 index 0000000000..0d49abde0b --- /dev/null +++ b/crates/nu-cmd-lang/build.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +fn main() -> shadow_rs::SdResult<()> { + // Look up the current Git commit ourselves instead of relying on shadow_rs, + // because shadow_rs does it in a really slow-to-compile way (it builds libgit2) + let hash = get_git_hash().unwrap_or_default(); + println!("cargo:rustc-env=NU_COMMIT_HASH={hash}"); + + shadow_rs::new() +} + +fn get_git_hash() -> Option { + Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok() + .filter(|output| output.status.success()) + .and_then(|output| String::from_utf8(output.stdout).ok()) + .map(|hash| hash.trim().to_string()) +} diff --git a/crates/nu-command/src/core_commands/alias.rs b/crates/nu-cmd-lang/src/core_commands/alias.rs similarity index 100% rename from crates/nu-command/src/core_commands/alias.rs rename to crates/nu-cmd-lang/src/core_commands/alias.rs diff --git a/crates/nu-command/src/core_commands/break_.rs b/crates/nu-cmd-lang/src/core_commands/break_.rs similarity index 100% rename from crates/nu-command/src/core_commands/break_.rs rename to crates/nu-cmd-lang/src/core_commands/break_.rs diff --git a/crates/nu-command/src/core_commands/commandline.rs b/crates/nu-cmd-lang/src/core_commands/commandline.rs similarity index 100% rename from crates/nu-command/src/core_commands/commandline.rs rename to crates/nu-cmd-lang/src/core_commands/commandline.rs diff --git a/crates/nu-command/src/core_commands/const_.rs b/crates/nu-cmd-lang/src/core_commands/const_.rs similarity index 100% rename from crates/nu-command/src/core_commands/const_.rs rename to crates/nu-cmd-lang/src/core_commands/const_.rs diff --git a/crates/nu-command/src/core_commands/continue_.rs b/crates/nu-cmd-lang/src/core_commands/continue_.rs similarity index 100% rename from crates/nu-command/src/core_commands/continue_.rs rename to crates/nu-cmd-lang/src/core_commands/continue_.rs diff --git a/crates/nu-command/src/core_commands/def.rs b/crates/nu-cmd-lang/src/core_commands/def.rs similarity index 100% rename from crates/nu-command/src/core_commands/def.rs rename to crates/nu-cmd-lang/src/core_commands/def.rs diff --git a/crates/nu-command/src/core_commands/def_env.rs b/crates/nu-cmd-lang/src/core_commands/def_env.rs similarity index 100% rename from crates/nu-command/src/core_commands/def_env.rs rename to crates/nu-cmd-lang/src/core_commands/def_env.rs diff --git a/crates/nu-command/src/core_commands/describe.rs b/crates/nu-cmd-lang/src/core_commands/describe.rs similarity index 99% rename from crates/nu-command/src/core_commands/describe.rs rename to crates/nu-cmd-lang/src/core_commands/describe.rs index 8b92454793..cf81187734 100644 --- a/crates/nu-command/src/core_commands/describe.rs +++ b/crates/nu-cmd-lang/src/core_commands/describe.rs @@ -76,6 +76,7 @@ impl Command for Describe { example: "'hello' | describe", result: Some(Value::test_string("string")), }, + /* Example { description: "Describe a stream of data, collecting it first", example: "[1 2 3] | each {|i| $i} | describe", @@ -86,6 +87,7 @@ impl Command for Describe { example: "[1 2 3] | each {|i| $i} | describe --no-collect", result: Some(Value::test_string("stream")), }, + */ ] } diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs similarity index 100% rename from crates/nu-command/src/core_commands/do_.rs rename to crates/nu-cmd-lang/src/core_commands/do_.rs diff --git a/crates/nu-command/src/core_commands/echo.rs b/crates/nu-cmd-lang/src/core_commands/echo.rs similarity index 100% rename from crates/nu-command/src/core_commands/echo.rs rename to crates/nu-cmd-lang/src/core_commands/echo.rs diff --git a/crates/nu-command/src/core_commands/error_make.rs b/crates/nu-cmd-lang/src/core_commands/error_make.rs similarity index 100% rename from crates/nu-command/src/core_commands/error_make.rs rename to crates/nu-cmd-lang/src/core_commands/error_make.rs diff --git a/crates/nu-command/src/core_commands/export.rs b/crates/nu-cmd-lang/src/core_commands/export.rs similarity index 100% rename from crates/nu-command/src/core_commands/export.rs rename to crates/nu-cmd-lang/src/core_commands/export.rs diff --git a/crates/nu-command/src/core_commands/export_alias.rs b/crates/nu-cmd-lang/src/core_commands/export_alias.rs similarity index 100% rename from crates/nu-command/src/core_commands/export_alias.rs rename to crates/nu-cmd-lang/src/core_commands/export_alias.rs diff --git a/crates/nu-command/src/core_commands/export_def.rs b/crates/nu-cmd-lang/src/core_commands/export_def.rs similarity index 100% rename from crates/nu-command/src/core_commands/export_def.rs rename to crates/nu-cmd-lang/src/core_commands/export_def.rs diff --git a/crates/nu-command/src/core_commands/export_def_env.rs b/crates/nu-cmd-lang/src/core_commands/export_def_env.rs similarity index 100% rename from crates/nu-command/src/core_commands/export_def_env.rs rename to crates/nu-cmd-lang/src/core_commands/export_def_env.rs diff --git a/crates/nu-command/src/core_commands/export_extern.rs b/crates/nu-cmd-lang/src/core_commands/export_extern.rs similarity index 100% rename from crates/nu-command/src/core_commands/export_extern.rs rename to crates/nu-cmd-lang/src/core_commands/export_extern.rs diff --git a/crates/nu-command/src/core_commands/export_use.rs b/crates/nu-cmd-lang/src/core_commands/export_use.rs similarity index 100% rename from crates/nu-command/src/core_commands/export_use.rs rename to crates/nu-cmd-lang/src/core_commands/export_use.rs diff --git a/crates/nu-command/src/core_commands/extern_.rs b/crates/nu-cmd-lang/src/core_commands/extern_.rs similarity index 100% rename from crates/nu-command/src/core_commands/extern_.rs rename to crates/nu-cmd-lang/src/core_commands/extern_.rs diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-cmd-lang/src/core_commands/for_.rs similarity index 100% rename from crates/nu-command/src/core_commands/for_.rs rename to crates/nu-cmd-lang/src/core_commands/for_.rs diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-cmd-lang/src/core_commands/help.rs similarity index 100% rename from crates/nu-command/src/core_commands/help.rs rename to crates/nu-cmd-lang/src/core_commands/help.rs diff --git a/crates/nu-command/src/core_commands/help_aliases.rs b/crates/nu-cmd-lang/src/core_commands/help_aliases.rs similarity index 100% rename from crates/nu-command/src/core_commands/help_aliases.rs rename to crates/nu-cmd-lang/src/core_commands/help_aliases.rs diff --git a/crates/nu-command/src/core_commands/help_commands.rs b/crates/nu-cmd-lang/src/core_commands/help_commands.rs similarity index 100% rename from crates/nu-command/src/core_commands/help_commands.rs rename to crates/nu-cmd-lang/src/core_commands/help_commands.rs diff --git a/crates/nu-command/src/core_commands/help_modules.rs b/crates/nu-cmd-lang/src/core_commands/help_modules.rs similarity index 100% rename from crates/nu-command/src/core_commands/help_modules.rs rename to crates/nu-cmd-lang/src/core_commands/help_modules.rs diff --git a/crates/nu-command/src/core_commands/help_operators.rs b/crates/nu-cmd-lang/src/core_commands/help_operators.rs similarity index 100% rename from crates/nu-command/src/core_commands/help_operators.rs rename to crates/nu-cmd-lang/src/core_commands/help_operators.rs diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-cmd-lang/src/core_commands/hide.rs similarity index 100% rename from crates/nu-command/src/core_commands/hide.rs rename to crates/nu-cmd-lang/src/core_commands/hide.rs diff --git a/crates/nu-command/src/core_commands/hide_env.rs b/crates/nu-cmd-lang/src/core_commands/hide_env.rs similarity index 100% rename from crates/nu-command/src/core_commands/hide_env.rs rename to crates/nu-cmd-lang/src/core_commands/hide_env.rs diff --git a/crates/nu-command/src/core_commands/if_.rs b/crates/nu-cmd-lang/src/core_commands/if_.rs similarity index 100% rename from crates/nu-command/src/core_commands/if_.rs rename to crates/nu-cmd-lang/src/core_commands/if_.rs diff --git a/crates/nu-command/src/core_commands/ignore.rs b/crates/nu-cmd-lang/src/core_commands/ignore.rs similarity index 100% rename from crates/nu-command/src/core_commands/ignore.rs rename to crates/nu-cmd-lang/src/core_commands/ignore.rs diff --git a/crates/nu-command/src/core_commands/let_.rs b/crates/nu-cmd-lang/src/core_commands/let_.rs similarity index 100% rename from crates/nu-command/src/core_commands/let_.rs rename to crates/nu-cmd-lang/src/core_commands/let_.rs diff --git a/crates/nu-command/src/core_commands/loop_.rs b/crates/nu-cmd-lang/src/core_commands/loop_.rs similarity index 100% rename from crates/nu-command/src/core_commands/loop_.rs rename to crates/nu-cmd-lang/src/core_commands/loop_.rs diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-cmd-lang/src/core_commands/mod.rs similarity index 96% rename from crates/nu-command/src/core_commands/mod.rs rename to crates/nu-cmd-lang/src/core_commands/mod.rs index 7999652aab..322a275cc7 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-cmd-lang/src/core_commands/mod.rs @@ -75,8 +75,8 @@ pub use try_::Try; pub use use_::Use; pub use version::Version; pub use while_::While; -#[cfg(feature = "plugin")] +//#[cfg(feature = "plugin")] mod register; -#[cfg(feature = "plugin")] +//#[cfg(feature = "plugin")] pub use register::Register; diff --git a/crates/nu-command/src/core_commands/module.rs b/crates/nu-cmd-lang/src/core_commands/module.rs similarity index 100% rename from crates/nu-command/src/core_commands/module.rs rename to crates/nu-cmd-lang/src/core_commands/module.rs diff --git a/crates/nu-command/src/core_commands/mut_.rs b/crates/nu-cmd-lang/src/core_commands/mut_.rs similarity index 100% rename from crates/nu-command/src/core_commands/mut_.rs rename to crates/nu-cmd-lang/src/core_commands/mut_.rs diff --git a/crates/nu-command/src/core_commands/overlay/command.rs b/crates/nu-cmd-lang/src/core_commands/overlay/command.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/command.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/command.rs diff --git a/crates/nu-command/src/core_commands/overlay/hide.rs b/crates/nu-cmd-lang/src/core_commands/overlay/hide.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/hide.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/hide.rs diff --git a/crates/nu-command/src/core_commands/overlay/list.rs b/crates/nu-cmd-lang/src/core_commands/overlay/list.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/list.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/list.rs diff --git a/crates/nu-command/src/core_commands/overlay/mod.rs b/crates/nu-cmd-lang/src/core_commands/overlay/mod.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/mod.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/mod.rs diff --git a/crates/nu-command/src/core_commands/overlay/new.rs b/crates/nu-cmd-lang/src/core_commands/overlay/new.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/new.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/new.rs diff --git a/crates/nu-command/src/core_commands/overlay/use_.rs b/crates/nu-cmd-lang/src/core_commands/overlay/use_.rs similarity index 100% rename from crates/nu-command/src/core_commands/overlay/use_.rs rename to crates/nu-cmd-lang/src/core_commands/overlay/use_.rs diff --git a/crates/nu-command/src/core_commands/register.rs b/crates/nu-cmd-lang/src/core_commands/register.rs similarity index 100% rename from crates/nu-command/src/core_commands/register.rs rename to crates/nu-cmd-lang/src/core_commands/register.rs diff --git a/crates/nu-command/src/core_commands/return_.rs b/crates/nu-cmd-lang/src/core_commands/return_.rs similarity index 100% rename from crates/nu-command/src/core_commands/return_.rs rename to crates/nu-cmd-lang/src/core_commands/return_.rs diff --git a/crates/nu-command/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs similarity index 100% rename from crates/nu-command/src/core_commands/try_.rs rename to crates/nu-cmd-lang/src/core_commands/try_.rs diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-cmd-lang/src/core_commands/use_.rs similarity index 100% rename from crates/nu-command/src/core_commands/use_.rs rename to crates/nu-cmd-lang/src/core_commands/use_.rs diff --git a/crates/nu-command/src/core_commands/version.rs b/crates/nu-cmd-lang/src/core_commands/version.rs similarity index 100% rename from crates/nu-command/src/core_commands/version.rs rename to crates/nu-cmd-lang/src/core_commands/version.rs diff --git a/crates/nu-command/src/core_commands/while_.rs b/crates/nu-cmd-lang/src/core_commands/while_.rs similarity index 100% rename from crates/nu-command/src/core_commands/while_.rs rename to crates/nu-cmd-lang/src/core_commands/while_.rs diff --git a/crates/nu-cmd-lang/src/default_context.rs b/crates/nu-cmd-lang/src/default_context.rs new file mode 100644 index 0000000000..3b51ae3cfb --- /dev/null +++ b/crates/nu-cmd-lang/src/default_context.rs @@ -0,0 +1,74 @@ +use nu_protocol::engine::{EngineState, StateWorkingSet}; + +use crate::*; + +pub fn create_default_context() -> EngineState { + let mut engine_state = EngineState::new(); + + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + macro_rules! bind_command { + ( $( $command:expr ),* $(,)? ) => { + $( working_set.add_decl(Box::new($command)); )* + }; + } + + // Core + bind_command! { + Alias, + Break, + Commandline, + Const, + Continue, + Def, + DefEnv, + Describe, + Do, + Echo, + ErrorMake, + ExportAlias, + ExportCommand, + ExportDef, + ExportDefEnv, + ExportExtern, + ExportUse, + Extern, + For, + Help, + HelpAliases, + HelpCommands, + HelpModules, + HelpOperators, + Hide, + HideEnv, + If, + Ignore, + Overlay, + OverlayUse, + OverlayList, + OverlayNew, + OverlayHide, + Let, + Loop, + Module, + Mut, + Return, + Try, + Use, + Version, + While, + }; + + //#[cfg(feature = "plugin")] + bind_command!(Register); + + working_set.render() + }; + + if let Err(err) = engine_state.merge_delta(delta) { + eprintln!("Error creating default context: {err:?}"); + } + + engine_state +} diff --git a/crates/nu-cmd-lang/src/example_test.rs b/crates/nu-cmd-lang/src/example_test.rs new file mode 100644 index 0000000000..6577ed6f36 --- /dev/null +++ b/crates/nu-cmd-lang/src/example_test.rs @@ -0,0 +1,298 @@ +#[cfg(test)] +use nu_protocol::engine::Command; + +#[cfg(test)] +pub fn test_examples(cmd: impl Command + 'static) { + test_examples::test_examples(cmd); +} + +#[cfg(test)] +mod test_examples { + use crate::{Break, Describe, Mut}; + use crate::{Echo, If, Let}; + use itertools::Itertools; + use nu_protocol::{ + ast::Block, + engine::{Command, EngineState, Stack, StateDelta, StateWorkingSet}, + Example, PipelineData, Signature, Span, Type, Value, + }; + use std::collections::HashSet; + + pub fn test_examples(cmd: impl Command + 'static) { + let examples = cmd.examples(); + let signature = cmd.signature(); + let mut engine_state = make_engine_state(cmd.clone_box()); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + + let mut witnessed_type_transformations = HashSet::<(Type, Type)>::new(); + + for example in examples { + if example.result.is_none() { + continue; + } + witnessed_type_transformations.extend( + check_example_input_and_output_types_match_command_signature( + &example, + &cwd, + &mut make_engine_state(cmd.clone_box()), + &signature.input_output_types, + signature.operates_on_cell_paths(), + signature.vectorizes_over_list, + ), + ); + check_example_evaluates_to_expected_output(&example, cwd.as_path(), &mut engine_state); + } + + check_all_signature_input_output_types_entries_have_examples( + signature, + witnessed_type_transformations, + ); + } + + fn make_engine_state(cmd: Box) -> Box { + let mut engine_state = Box::new(EngineState::new()); + + let delta = { + // Base functions that are needed for testing + // Try to keep this working set small to keep tests running as fast as possible + let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Break)); + working_set.add_decl(Box::new(Describe)); + working_set.add_decl(Box::new(Echo)); + working_set.add_decl(Box::new(If)); + working_set.add_decl(Box::new(Let)); + working_set.add_decl(Box::new(Mut)); + + // Adding the command that is being tested to the working set + working_set.add_decl(cmd); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + engine_state + } + + fn check_example_input_and_output_types_match_command_signature( + example: &Example, + cwd: &std::path::Path, + engine_state: &mut Box, + signature_input_output_types: &Vec<(Type, Type)>, + signature_operates_on_cell_paths: bool, + signature_vectorizes_over_list: bool, + ) -> HashSet<(Type, Type)> { + let mut witnessed_type_transformations = HashSet::<(Type, Type)>::new(); + + // Skip tests that don't have results to compare to + if let Some(example_output) = example.result.as_ref() { + if let Some(example_input_type) = + eval_pipeline_without_terminal_expression(example.example, cwd, engine_state) + { + let example_input_type = example_input_type.get_type(); + let example_output_type = example_output.get_type(); + + let example_matches_signature = + signature_input_output_types + .iter() + .any(|(sig_in_type, sig_out_type)| { + example_input_type.is_subtype(sig_in_type) + && example_output_type.is_subtype(sig_out_type) + && { + witnessed_type_transformations + .insert((sig_in_type.clone(), sig_out_type.clone())); + true + } + }); + + // The example type checks as vectorization over an input list if both: + // 1. The command is declared to vectorize over list input. + // 2. There exists an entry t -> u in the type map such that the + // example_input_type is a subtype of list and the + // example_output_type is a subtype of list. + let example_matches_signature_via_vectorization_over_list = + signature_vectorizes_over_list + && match &example_input_type { + Type::List(ex_in_type) => { + match signature_input_output_types.iter().find_map( + |(sig_in_type, sig_out_type)| { + if ex_in_type.is_subtype(sig_in_type) { + Some((sig_in_type, sig_out_type)) + } else { + None + } + }, + ) { + Some((sig_in_type, sig_out_type)) => match &example_output_type + { + Type::List(ex_out_type) + if ex_out_type.is_subtype(sig_out_type) => + { + witnessed_type_transformations.insert(( + sig_in_type.clone(), + sig_out_type.clone(), + )); + true + } + _ => false, + }, + None => false, + } + } + _ => false, + }; + + // The example type checks as a cell path operation if both: + // 1. The command is declared to operate on cell paths. + // 2. The example_input_type is list or record or table, and the example + // output shape is the same as the input shape. + let example_matches_signature_via_cell_path_operation = + signature_operates_on_cell_paths + && example_input_type.accepts_cell_paths() + // TODO: This is too permissive; it should make use of the signature.input_output_types at least. + && example_output_type.to_shape() == example_input_type.to_shape(); + + if !(example_matches_signature + || example_matches_signature_via_vectorization_over_list + || example_matches_signature_via_cell_path_operation) + { + panic!( + "The example `{}` demonstrates a transformation of type {:?} -> {:?}. \ + However, this does not match the declared signature: {:?}.{} \ + For this command, `vectorizes_over_list` is {} and `operates_on_cell_paths()` is {}.", + example.example, + example_input_type, + example_output_type, + signature_input_output_types, + if signature_input_output_types.is_empty() { " (Did you forget to declare the input and output types for the command?)" } else { "" }, + signature_vectorizes_over_list, + signature_operates_on_cell_paths + ); + }; + }; + } + witnessed_type_transformations + } + + fn check_example_evaluates_to_expected_output( + example: &Example, + cwd: &std::path::Path, + engine_state: &mut Box, + ) { + let mut stack = Stack::new(); + + // Set up PWD + stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy())); + + engine_state + .merge_env(&mut stack, cwd) + .expect("Error merging environment"); + + let empty_input = PipelineData::empty(); + let result = eval(example.example, empty_input, cwd, engine_state); + + // Note. Value implements PartialEq for Bool, Int, Float, String and Block + // If the command you are testing requires to compare another case, then + // you need to define its equality in the Value struct + if let Some(expected) = example.result.as_ref() { + assert_eq!( + &result, expected, + "The example result differs from the expected value", + ) + } + } + + fn check_all_signature_input_output_types_entries_have_examples( + signature: Signature, + witnessed_type_transformations: HashSet<(Type, Type)>, + ) { + let declared_type_transformations = + HashSet::from_iter(signature.input_output_types.into_iter()); + assert!( + witnessed_type_transformations.is_subset(&declared_type_transformations), + "This should not be possible (bug in test): the type transformations \ + collected in the course of matching examples to the signature type map \ + contain type transformations not present in the signature type map." + ); + + if !signature.allow_variants_without_examples { + assert_eq!( + witnessed_type_transformations, + declared_type_transformations, + "There are entries in the signature type map which do not correspond to any example: \ + {:?}", + declared_type_transformations + .difference(&witnessed_type_transformations) + .map(|(s1, s2)| format!("{s1} -> {s2}")) + .join(", ") + ); + } + } + + fn eval( + contents: &str, + input: PipelineData, + cwd: &std::path::Path, + engine_state: &mut Box, + ) -> Value { + let (block, delta) = parse(contents, engine_state); + eval_block(block, input, cwd, engine_state, delta) + } + + fn parse(contents: &str, engine_state: &EngineState) -> (Block, StateDelta) { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = + nu_parser::parse(&mut working_set, None, contents.as_bytes(), false, &[]); + + if let Some(err) = err { + panic!("test parse error in `{contents}`: {err:?}") + } + + (output, working_set.render()) + } + + fn eval_block( + block: Block, + input: PipelineData, + cwd: &std::path::Path, + engine_state: &mut Box, + delta: StateDelta, + ) -> Value { + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let mut stack = Stack::new(); + + stack.add_env_var("PWD".to_string(), Value::test_string(cwd.to_string_lossy())); + + match nu_engine::eval_block(engine_state, &mut stack, &block, input, true, true) { + Err(err) => panic!("test eval error in `{}`: {:?}", "TODO", err), + Ok(result) => result.into_value(Span::test_data()), + } + } + + fn eval_pipeline_without_terminal_expression( + src: &str, + cwd: &std::path::Path, + engine_state: &mut Box, + ) -> Option { + let (mut block, delta) = parse(src, engine_state); + if block.pipelines.len() == 1 { + let n_expressions = block.pipelines[0].elements.len(); + block.pipelines[0].elements.truncate(&n_expressions - 1); + + if !block.pipelines[0].elements.is_empty() { + let empty_input = PipelineData::empty(); + Some(eval_block(block, empty_input, cwd, engine_state, delta)) + } else { + Some(Value::nothing(Span::test_data())) + } + } else { + // E.g. multiple semicolon-separated statements + None + } + } +} diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs new file mode 100644 index 0000000000..e25223281e --- /dev/null +++ b/crates/nu-cmd-lang/src/lib.rs @@ -0,0 +1,8 @@ +mod core_commands; +mod default_context; +mod example_test; + +pub use core_commands::*; +pub use default_context::*; +#[cfg(test)] +pub use example_test::test_examples; diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index fecb900f78..e4b34770fd 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,6 +15,7 @@ bench = false [dependencies] nu-ansi-term = "0.46.0" +nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.76.1" } nu-color-config = { path = "../nu-color-config", version = "0.76.1" } nu-engine = { path = "../nu-engine", version = "0.76.1" } nu-explore = { path = "../nu-explore", version = "0.76.1" } diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index 983e13a005..697ce7bd5f 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -8,7 +8,7 @@ use nu_protocol::{ use super::eager::ToDataFrame; use super::expressions::ExprCol; use super::lazy::{LazyCollect, ToLazyFrame}; -use crate::Let; +use nu_cmd_lang::Let; pub fn test_dataframe(cmds: Vec>) { if cmds.is_empty() { diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 28b8c1a0f4..51492dbd3a 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -3,7 +3,7 @@ use nu_protocol::engine::{EngineState, StateWorkingSet}; use crate::*; pub fn create_default_context() -> EngineState { - let mut engine_state = EngineState::new(); + let mut engine_state = nu_cmd_lang::create_default_context(); let delta = { let mut working_set = StateWorkingSet::new(&engine_state); @@ -26,52 +26,6 @@ pub fn create_default_context() -> EngineState { #[cfg(feature = "sqlite")] add_database_decls(&mut working_set); - // Core - bind_command! { - Alias, - Break, - Commandline, - Const, - Continue, - Def, - DefEnv, - Describe, - Do, - Echo, - ErrorMake, - ExportAlias, - ExportCommand, - ExportDef, - ExportDefEnv, - ExportExtern, - ExportUse, - Extern, - For, - Help, - HelpAliases, - HelpCommands, - HelpModules, - HelpOperators, - Hide, - HideEnv, - If, - Ignore, - Overlay, - OverlayUse, - OverlayList, - OverlayNew, - OverlayHide, - Let, - Loop, - Module, - Mut, - Return, - Try, - Use, - Version, - While, - }; - // Charts bind_command! { Histogram @@ -498,9 +452,6 @@ pub fn create_default_context() -> EngineState { MathEvalDeprecated, }; - #[cfg(feature = "plugin")] - bind_command!(Register); - working_set.render() }; diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 0c7a5f347f..52c9ccf6af 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -9,12 +9,13 @@ pub fn test_examples(cmd: impl Command + 'static) { #[cfg(test)] mod test_examples { use super::super::{ - Ansi, Date, Echo, Enumerate, Flatten, From, Get, If, Into, IntoString, Let, LetEnv, Math, - MathEuler, MathPi, MathRound, ParEach, Path, Random, Sort, SortBy, Split, SplitColumn, - SplitRow, Str, StrJoin, StrLength, StrReplace, Update, Url, Values, Wrap, + Ansi, Date, Enumerate, Flatten, From, Get, Into, IntoString, LetEnv, Math, MathEuler, + MathPi, MathRound, ParEach, Path, Random, Sort, SortBy, Split, SplitColumn, SplitRow, Str, + StrJoin, StrLength, StrReplace, Update, Url, Values, Wrap, }; - use crate::{Break, Each, Mut, To}; + use crate::{Each, To}; use itertools::Itertools; + use nu_cmd_lang::{Break, Echo, If, Let, Mut}; use nu_protocol::{ ast::Block, engine::{Command, EngineState, Stack, StateDelta, StateWorkingSet}, diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index daf6ee8d28..1e82e855cf 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -1,4 +1,4 @@ -use crate::help::highlight_search_string; +use nu_cmd_lang::help::highlight_search_string; use fancy_regex::Regex; use lscolors::{Color as LsColors_Color, LsColors, Style as LsColors_Style}; diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 3c72e0853c..a3ac6bd737 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -2,7 +2,6 @@ mod bits; mod bytes; mod charting; mod conversions; -mod core_commands; mod date; mod debug; mod default_context; @@ -33,7 +32,6 @@ pub use bits::*; pub use bytes::*; pub use charting::*; pub use conversions::*; -pub use core_commands::*; pub use date::*; pub use debug::*; pub use default_context::*;