mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 15:25:06 +02:00
Add infrastructure for experimental options (#16028)
Co-authored-by: Bahex <Bahex@users.noreply.github.com>
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -3541,6 +3541,7 @@ dependencies = [
|
|||||||
"nu-cmd-plugin",
|
"nu-cmd-plugin",
|
||||||
"nu-command",
|
"nu-command",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
|
"nu-experimental",
|
||||||
"nu-explore",
|
"nu-explore",
|
||||||
"nu-lsp",
|
"nu-lsp",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
@ -3658,6 +3659,7 @@ dependencies = [
|
|||||||
"miette",
|
"miette",
|
||||||
"nu-cmd-base",
|
"nu-cmd-base",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
|
"nu-experimental",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-utils",
|
"nu-utils",
|
||||||
@ -3736,6 +3738,7 @@ dependencies = [
|
|||||||
"nu-cmd-lang",
|
"nu-cmd-lang",
|
||||||
"nu-color-config",
|
"nu-color-config",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
|
"nu-experimental",
|
||||||
"nu-glob",
|
"nu-glob",
|
||||||
"nu-json",
|
"nu-json",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
@ -3829,6 +3832,14 @@ dependencies = [
|
|||||||
"nu-utils",
|
"nu-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-experimental"
|
||||||
|
version = "0.105.2"
|
||||||
|
dependencies = [
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-explore"
|
name = "nu-explore"
|
||||||
version = "0.105.2"
|
version = "0.105.2"
|
||||||
|
37
Cargo.toml
37
Cargo.toml
@ -24,36 +24,37 @@ pkg-fmt = "zip"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"crates/nu_plugin_custom_values",
|
||||||
|
"crates/nu_plugin_example",
|
||||||
|
"crates/nu_plugin_formats",
|
||||||
|
"crates/nu_plugin_gstat",
|
||||||
|
"crates/nu_plugin_inc",
|
||||||
|
"crates/nu_plugin_polars",
|
||||||
|
"crates/nu_plugin_query",
|
||||||
|
"crates/nu_plugin_stress_internals",
|
||||||
"crates/nu-cli",
|
"crates/nu-cli",
|
||||||
"crates/nu-engine",
|
|
||||||
"crates/nu-parser",
|
|
||||||
"crates/nu-system",
|
|
||||||
"crates/nu-cmd-base",
|
"crates/nu-cmd-base",
|
||||||
"crates/nu-cmd-extra",
|
"crates/nu-cmd-extra",
|
||||||
"crates/nu-cmd-lang",
|
"crates/nu-cmd-lang",
|
||||||
"crates/nu-cmd-plugin",
|
"crates/nu-cmd-plugin",
|
||||||
"crates/nu-command",
|
|
||||||
"crates/nu-color-config",
|
"crates/nu-color-config",
|
||||||
|
"crates/nu-command",
|
||||||
|
"crates/nu-derive-value",
|
||||||
|
"crates/nu-engine",
|
||||||
|
"crates/nu-experimental",
|
||||||
"crates/nu-explore",
|
"crates/nu-explore",
|
||||||
"crates/nu-json",
|
"crates/nu-json",
|
||||||
"crates/nu-lsp",
|
"crates/nu-lsp",
|
||||||
"crates/nu-pretty-hex",
|
"crates/nu-parser",
|
||||||
"crates/nu-protocol",
|
|
||||||
"crates/nu-derive-value",
|
|
||||||
"crates/nu-plugin",
|
|
||||||
"crates/nu-plugin-core",
|
"crates/nu-plugin-core",
|
||||||
"crates/nu-plugin-engine",
|
"crates/nu-plugin-engine",
|
||||||
"crates/nu-plugin-protocol",
|
"crates/nu-plugin-protocol",
|
||||||
"crates/nu-plugin-test-support",
|
"crates/nu-plugin-test-support",
|
||||||
"crates/nu_plugin_inc",
|
"crates/nu-plugin",
|
||||||
"crates/nu_plugin_gstat",
|
"crates/nu-pretty-hex",
|
||||||
"crates/nu_plugin_example",
|
"crates/nu-protocol",
|
||||||
"crates/nu_plugin_query",
|
|
||||||
"crates/nu_plugin_custom_values",
|
|
||||||
"crates/nu_plugin_formats",
|
|
||||||
"crates/nu_plugin_polars",
|
|
||||||
"crates/nu_plugin_stress_internals",
|
|
||||||
"crates/nu-std",
|
"crates/nu-std",
|
||||||
|
"crates/nu-system",
|
||||||
"crates/nu-table",
|
"crates/nu-table",
|
||||||
"crates/nu-term-grid",
|
"crates/nu-term-grid",
|
||||||
"crates/nu-test-support",
|
"crates/nu-test-support",
|
||||||
@ -163,6 +164,7 @@ syn = "2.0"
|
|||||||
sysinfo = "0.33"
|
sysinfo = "0.33"
|
||||||
tabled = { version = "0.20", default-features = false }
|
tabled = { version = "0.20", default-features = false }
|
||||||
tempfile = "3.20"
|
tempfile = "3.20"
|
||||||
|
thiserror = "2.0.12"
|
||||||
titlecase = "3.6"
|
titlecase = "3.6"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
trash = "5.2"
|
trash = "5.2"
|
||||||
@ -203,11 +205,12 @@ workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
|
nu-cli = { path = "./crates/nu-cli", version = "0.105.2" }
|
||||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
|
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.105.2" }
|
||||||
|
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
|
||||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
|
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.105.2" }
|
||||||
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.2", optional = true }
|
nu-cmd-plugin = { path = "./crates/nu-cmd-plugin", version = "0.105.2", optional = true }
|
||||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.105.2" }
|
|
||||||
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
|
nu-command = { path = "./crates/nu-command", version = "0.105.2", default-features = false, features = ["os"] }
|
||||||
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
|
nu-engine = { path = "./crates/nu-engine", version = "0.105.2" }
|
||||||
|
nu-experimental = { path = "./crates/nu-experimental", version = "0.105.2" }
|
||||||
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
|
nu-explore = { path = "./crates/nu-explore", version = "0.105.2" }
|
||||||
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
|
nu-lsp = { path = "./crates/nu-lsp/", version = "0.105.2" }
|
||||||
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
|
nu-parser = { path = "./crates/nu-parser", version = "0.105.2" }
|
||||||
|
@ -16,6 +16,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
|
nu-experimental = { path = "../nu-experimental", version = "0.105.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
nu-protocol = { path = "../nu-protocol", version = "0.105.2", default-features = false }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||||
|
@ -188,6 +188,17 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result<PipelineData, S
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record.push(
|
||||||
|
"experimental_options",
|
||||||
|
Value::string(
|
||||||
|
nu_experimental::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|option| format!("{}={}", option.identifier(), option.get()))
|
||||||
|
.join(", "),
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Value::record(record, span).into_pipeline_data())
|
Ok(Value::record(record, span).into_pipeline_data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,9 +16,11 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
nu-ansi-term = { workspace = true }
|
||||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
nu-cmd-base = { path = "../nu-cmd-base", version = "0.105.2" }
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
nu-color-config = { path = "../nu-color-config", version = "0.105.2" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
nu-engine = { path = "../nu-engine", version = "0.105.2", default-features = false }
|
||||||
|
nu-experimental = { path = "../nu-experimental", version = "0.105.2" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||||
nu-json = { path = "../nu-json", version = "0.105.2" }
|
nu-json = { path = "../nu-json", version = "0.105.2" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
nu-parser = { path = "../nu-parser", version = "0.105.2" }
|
||||||
@ -29,7 +31,6 @@ nu-system = { path = "../nu-system", version = "0.105.2" }
|
|||||||
nu-table = { path = "../nu-table", version = "0.105.2" }
|
nu-table = { path = "../nu-table", version = "0.105.2" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.105.2" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||||
nu-ansi-term = { workspace = true }
|
|
||||||
nuon = { path = "../nuon", version = "0.105.2" }
|
nuon = { path = "../nuon", version = "0.105.2" }
|
||||||
|
|
||||||
alphanumeric-sort = { workspace = true }
|
alphanumeric-sort = { workspace = true }
|
||||||
|
64
crates/nu-command/src/debug/experimental_options.rs
Normal file
64
crates/nu-command/src/debug/experimental_options.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_experimental::Status;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DebugExperimentalOptions;
|
||||||
|
|
||||||
|
impl Command for DebugExperimentalOptions {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"debug experimental-options"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::new(self.name())
|
||||||
|
.input_output_type(
|
||||||
|
Type::Nothing,
|
||||||
|
Type::Table(Box::from([
|
||||||
|
(String::from("identifier"), Type::String),
|
||||||
|
(String::from("enabled"), Type::Bool),
|
||||||
|
(String::from("status"), Type::String),
|
||||||
|
(String::from("description"), Type::String),
|
||||||
|
])),
|
||||||
|
)
|
||||||
|
.add_help()
|
||||||
|
.category(Category::Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Show all experimental options."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(PipelineData::Value(
|
||||||
|
Value::list(
|
||||||
|
nu_experimental::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|option| {
|
||||||
|
Value::record(
|
||||||
|
nu_protocol::record! {
|
||||||
|
"identifier" => Value::string(option.identifier(), call.head),
|
||||||
|
"enabled" => Value::bool(option.get(), call.head),
|
||||||
|
"status" => Value::string(match option.status() {
|
||||||
|
Status::OptIn => "opt-in",
|
||||||
|
Status::OptOut => "opt-out",
|
||||||
|
Status::DeprecatedDiscard => "deprecated-discard",
|
||||||
|
Status::DeprecatedDefault => "deprecated-default"
|
||||||
|
}, call.head),
|
||||||
|
"description" => Value::string(option.description(), call.head),
|
||||||
|
},
|
||||||
|
call.head,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
call.head,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
mod ast;
|
mod ast;
|
||||||
mod debug_;
|
mod debug_;
|
||||||
mod env;
|
mod env;
|
||||||
|
mod experimental_options;
|
||||||
mod explain;
|
mod explain;
|
||||||
mod info;
|
mod info;
|
||||||
mod inspect;
|
mod inspect;
|
||||||
@ -21,6 +22,7 @@ mod view_span;
|
|||||||
pub use ast::Ast;
|
pub use ast::Ast;
|
||||||
pub use debug_::Debug;
|
pub use debug_::Debug;
|
||||||
pub use env::DebugEnv;
|
pub use env::DebugEnv;
|
||||||
|
pub use experimental_options::DebugExperimentalOptions;
|
||||||
pub use explain::Explain;
|
pub use explain::Explain;
|
||||||
pub use info::DebugInfo;
|
pub use info::DebugInfo;
|
||||||
pub use inspect::Inspect;
|
pub use inspect::Inspect;
|
||||||
|
@ -153,6 +153,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
Ast,
|
Ast,
|
||||||
Debug,
|
Debug,
|
||||||
DebugEnv,
|
DebugEnv,
|
||||||
|
DebugExperimentalOptions,
|
||||||
DebugInfo,
|
DebugInfo,
|
||||||
DebugProfile,
|
DebugProfile,
|
||||||
Explain,
|
Explain,
|
||||||
|
12
crates/nu-experimental/Cargo.toml
Normal file
12
crates/nu-experimental/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nushell Project Developers"]
|
||||||
|
description = "Nushell experimental options"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-experimental"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-experimental"
|
||||||
|
version = "0.105.2"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools.workspace = true
|
||||||
|
thiserror.workspace = true
|
203
crates/nu-experimental/src/lib.rs
Normal file
203
crates/nu-experimental/src/lib.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
//! Experimental Options for the Nu codebase.
|
||||||
|
//!
|
||||||
|
//! This crate defines all experimental options used in Nushell.
|
||||||
|
//!
|
||||||
|
//! An [`ExperimentalOption`] is basically a fancy global boolean.
|
||||||
|
//! It should be set very early during initialization and lets us switch between old and new
|
||||||
|
//! behavior for parts of the system.
|
||||||
|
//!
|
||||||
|
//! The goal is to have a consistent way to handle experimental flags across the codebase, and to
|
||||||
|
//! make it easy to find all available options.
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! Using an option is simple:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! if nu_experimental::EXAMPLE.get() {
|
||||||
|
//! // new behavior
|
||||||
|
//! } else {
|
||||||
|
//! // old behavior
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Adding New Options
|
||||||
|
//!
|
||||||
|
//! 1. Create a new module in `options.rs`.
|
||||||
|
//! 2. Define a marker struct and implement `ExperimentalOptionMarker` for it.
|
||||||
|
//! 3. Add a new static using `ExperimentalOption::new`.
|
||||||
|
//! 4. Add the static to [`ALL`].
|
||||||
|
//!
|
||||||
|
//! That's it. See [`EXAMPLE`] in `options/example.rs` for a complete example.
|
||||||
|
//!
|
||||||
|
//! # For Users
|
||||||
|
//!
|
||||||
|
//! Users can view enabled options using either `version` or `debug experimental-options`.
|
||||||
|
//!
|
||||||
|
//! To enable or disable options, use either the `NU_EXPERIMENTAL_OPTIONS` environment variable
|
||||||
|
//! (see [`ENV`]), or pass them via CLI using `--experimental-options`, e.g.:
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! nu --experimental-options=[example]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # For Embedders
|
||||||
|
//!
|
||||||
|
//! If you're embedding Nushell, prefer using [`parse_env`] or [`parse_iter`] to load options.
|
||||||
|
//!
|
||||||
|
//! `parse_iter` is useful if you want to feed in values from other sources.
|
||||||
|
//! Since options are expected to stay stable during runtime, make sure to do this early.
|
||||||
|
//!
|
||||||
|
//! You can also call [`ExperimentalOption::set`] manually, but be careful with that.
|
||||||
|
|
||||||
|
use crate::util::AtomicMaybe;
|
||||||
|
use std::{fmt::Debug, sync::atomic::Ordering};
|
||||||
|
|
||||||
|
mod options;
|
||||||
|
mod parse;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use options::*;
|
||||||
|
pub use parse::*;
|
||||||
|
|
||||||
|
/// The status of an experimental option.
|
||||||
|
///
|
||||||
|
/// An option can either be disabled by default ([`OptIn`](Self::OptIn)) or enabled by default
|
||||||
|
/// ([`OptOut`](Self::OptOut)), depending on its expected stability.
|
||||||
|
///
|
||||||
|
/// Experimental options can be deprecated in two ways:
|
||||||
|
/// - If the feature becomes default behavior, it's marked as
|
||||||
|
/// [`DeprecatedDefault`](Self::DeprecatedDefault).
|
||||||
|
/// - If the feature is being fully removed, it's marked as
|
||||||
|
/// [`DeprecatedDiscard`](Self::DeprecatedDiscard) and triggers a warning.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Status {
|
||||||
|
/// Disabled by default.
|
||||||
|
OptIn,
|
||||||
|
/// Enabled by default.
|
||||||
|
OptOut,
|
||||||
|
/// Deprecated as an experimental option; now default behavior.
|
||||||
|
DeprecatedDefault,
|
||||||
|
/// Deprecated; the feature will be removed and triggers a warning.
|
||||||
|
DeprecatedDiscard,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Experimental option (aka feature flag).
|
||||||
|
///
|
||||||
|
/// This struct holds one experimental option that can change some part of Nushell's behavior.
|
||||||
|
/// These options let users opt in or out of experimental changes while keeping the rest stable.
|
||||||
|
/// They're useful for testing new ideas and giving users a way to go back to older behavior if needed.
|
||||||
|
///
|
||||||
|
/// You can find all options in the statics of [`nu_experimental`](crate).
|
||||||
|
/// Everything there, except [`ALL`], is a toggleable option.
|
||||||
|
/// `ALL` gives a full list and can be used to check which options are set.
|
||||||
|
///
|
||||||
|
/// The [`Debug`] implementation shows the option's identifier, stability, and current value.
|
||||||
|
/// To also include the description in the output, use the
|
||||||
|
/// [plus sign](std::fmt::Formatter::sign_plus), e.g. `format!("{OPTION:+#?}")`.
|
||||||
|
pub struct ExperimentalOption {
|
||||||
|
value: AtomicMaybe,
|
||||||
|
marker: &'static (dyn DynExperimentalOptionMarker + Send + Sync),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExperimentalOption {
|
||||||
|
/// Construct a new `ExperimentalOption`.
|
||||||
|
///
|
||||||
|
/// This should only be used to define a single static for a marker.
|
||||||
|
pub(crate) const fn new(
|
||||||
|
marker: &'static (dyn DynExperimentalOptionMarker + Send + Sync),
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
value: AtomicMaybe::new(None),
|
||||||
|
marker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn identifier(&self) -> &'static str {
|
||||||
|
self.marker.identifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(&self) -> &'static str {
|
||||||
|
self.marker.description()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self) -> Status {
|
||||||
|
self.marker.status()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> bool {
|
||||||
|
self.value
|
||||||
|
.load(Ordering::Relaxed)
|
||||||
|
.unwrap_or_else(|| match self.marker.status() {
|
||||||
|
Status::OptIn => false,
|
||||||
|
Status::OptOut => true,
|
||||||
|
Status::DeprecatedDiscard => false,
|
||||||
|
Status::DeprecatedDefault => false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the state of an experimental option.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This method is unsafe to emphasize that experimental options are not designed to change
|
||||||
|
/// dynamically at runtime.
|
||||||
|
/// Changing their state at arbitrary points can lead to inconsistent behavior.
|
||||||
|
/// You should set experimental options only during initialization, before the application fully
|
||||||
|
/// starts.
|
||||||
|
pub unsafe fn set(&self, value: bool) {
|
||||||
|
self.value.store(value, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unsets an experimental option, resetting it to an uninitialized state.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Like [`set`](Self::set), this method is unsafe to highlight that experimental options should
|
||||||
|
/// remain stable during runtime.
|
||||||
|
/// Only unset options in controlled, initialization contexts to avoid unpredictable behavior.
|
||||||
|
pub unsafe fn unset(&self) {
|
||||||
|
self.value.store(None, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ExperimentalOption {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let add_description = f.sign_plus();
|
||||||
|
let mut debug_struct = f.debug_struct("ExperimentalOption");
|
||||||
|
debug_struct.field("identifier", &self.identifier());
|
||||||
|
debug_struct.field("value", &self.get());
|
||||||
|
debug_struct.field("stability", &self.status());
|
||||||
|
if add_description {
|
||||||
|
debug_struct.field("description", &self.description());
|
||||||
|
}
|
||||||
|
debug_struct.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ExperimentalOption {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
// if both underlying atomics point to the same value, we talk about the same option
|
||||||
|
self.value.as_ptr() == other.value.as_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ExperimentalOption {}
|
||||||
|
|
||||||
|
pub(crate) trait DynExperimentalOptionMarker {
|
||||||
|
fn identifier(&self) -> &'static str;
|
||||||
|
fn description(&self) -> &'static str;
|
||||||
|
fn status(&self) -> Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: options::ExperimentalOptionMarker> DynExperimentalOptionMarker for M {
|
||||||
|
fn identifier(&self) -> &'static str {
|
||||||
|
M::IDENTIFIER
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &'static str {
|
||||||
|
M::DESCRIPTION
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self) -> Status {
|
||||||
|
M::STATUS
|
||||||
|
}
|
||||||
|
}
|
20
crates/nu-experimental/src/options/example.rs
Normal file
20
crates/nu-experimental/src/options/example.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Example experimental option.
|
||||||
|
///
|
||||||
|
/// This shows how experimental options should be implemented and documented.
|
||||||
|
/// Reading this static's documentation alone should clearly explain what the
|
||||||
|
/// option changes and how it interacts with the rest of the codebase.
|
||||||
|
///
|
||||||
|
/// Use this pattern when adding real experimental options.
|
||||||
|
pub static EXAMPLE: ExperimentalOption = ExperimentalOption::new(&Example);
|
||||||
|
|
||||||
|
// No documentation needed here since this type isn't public.
|
||||||
|
// The static above provides all necessary details.
|
||||||
|
struct Example;
|
||||||
|
|
||||||
|
impl ExperimentalOptionMarker for Example {
|
||||||
|
const IDENTIFIER: &'static str = "example";
|
||||||
|
const DESCRIPTION: &'static str = "This is an example of an experimental option.";
|
||||||
|
const STATUS: Status = Status::DeprecatedDiscard;
|
||||||
|
}
|
92
crates/nu-experimental/src/options/mod.rs
Normal file
92
crates/nu-experimental/src/options/mod.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#![allow(
|
||||||
|
private_interfaces,
|
||||||
|
reason = "The marker structs don't need to be exposed, only the static values."
|
||||||
|
)]
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
mod example;
|
||||||
|
|
||||||
|
/// Marker trait for defining experimental options.
|
||||||
|
///
|
||||||
|
/// Implement this trait to mark a struct as metadata for an [`ExperimentalOption`].
|
||||||
|
/// It provides all necessary information about an experimental feature directly in code,
|
||||||
|
/// without needing external documentation.
|
||||||
|
///
|
||||||
|
/// The `STATUS` field is especially important as it controls whether the feature is enabled
|
||||||
|
/// by default and how users should interpret its reliability.
|
||||||
|
pub(crate) trait ExperimentalOptionMarker {
|
||||||
|
/// Unique identifier for this experimental option.
|
||||||
|
///
|
||||||
|
/// Must be a valid Rust identifier.
|
||||||
|
/// Used when parsing to toggle specific experimental options,
|
||||||
|
/// and may also serve as a user-facing label.
|
||||||
|
const IDENTIFIER: &'static str;
|
||||||
|
|
||||||
|
/// Brief description explaining what this option changes.
|
||||||
|
///
|
||||||
|
/// Displayed to users in help messages or summaries without needing to visit external docs.
|
||||||
|
const DESCRIPTION: &'static str;
|
||||||
|
|
||||||
|
/// Indicates the status of an experimental status.
|
||||||
|
///
|
||||||
|
/// Options marked [`Status::OptIn`] are disabled by default while options marked with
|
||||||
|
/// [`Status::OptOut`] are enabled by default.
|
||||||
|
/// Experimental options that stabilize should be marked as [`Status::DeprecatedDefault`] while
|
||||||
|
/// options that will be removed should be [`Status::DeprecatedDiscard`].
|
||||||
|
const STATUS: Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export only the static values.
|
||||||
|
// The marker structs are not relevant and needlessly clutter the generated docs.
|
||||||
|
pub use example::EXAMPLE;
|
||||||
|
|
||||||
|
// Include all experimental option statics in here.
|
||||||
|
// This will test them and add them to the parsing list.
|
||||||
|
|
||||||
|
/// A list of all available experimental options.
|
||||||
|
///
|
||||||
|
/// Use this to show users every experimental option, including their descriptions,
|
||||||
|
/// identifiers, and current state.
|
||||||
|
pub static ALL: &[&ExperimentalOption] = &[&EXAMPLE];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_identifiers_are_unique() {
|
||||||
|
let list: Vec<_> = ALL.iter().map(|opt| opt.identifier()).collect();
|
||||||
|
let set: HashSet<_> = HashSet::from_iter(&list);
|
||||||
|
assert_eq!(list.len(), set.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_identifiers_are_valid() {
|
||||||
|
for option in ALL {
|
||||||
|
let identifier = option.identifier();
|
||||||
|
assert!(!identifier.is_empty());
|
||||||
|
|
||||||
|
let mut chars = identifier.chars();
|
||||||
|
let first = chars.next().expect("not empty");
|
||||||
|
assert!(first.is_alphabetic());
|
||||||
|
assert!(first.is_lowercase());
|
||||||
|
|
||||||
|
for char in chars {
|
||||||
|
assert!(char.is_alphanumeric());
|
||||||
|
if char.is_alphabetic() {
|
||||||
|
assert!(char.is_lowercase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assert_description_not_empty() {
|
||||||
|
for option in ALL {
|
||||||
|
assert!(!option.description().is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
crates/nu-experimental/src/parse.rs
Normal file
137
crates/nu-experimental/src/parse.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use crate::{ALL, ExperimentalOption, Status};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::{borrow::Cow, env, ops::Range, sync::atomic::Ordering};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// Environment variable used to load experimental options from.
|
||||||
|
///
|
||||||
|
/// May be used like this: `NU_EXPERIMENTAL_OPTIONS=example nu`.
|
||||||
|
pub const ENV: &str = "NU_EXPERIMENTAL_OPTIONS";
|
||||||
|
|
||||||
|
/// Warnings that can happen while parsing experimental options.
|
||||||
|
#[derive(Debug, Clone, Error, Eq, PartialEq)]
|
||||||
|
pub enum ParseWarning {
|
||||||
|
/// The given identifier doesn't match any known experimental option.
|
||||||
|
#[error("Unknown experimental option `{0}`")]
|
||||||
|
Unknown(String),
|
||||||
|
|
||||||
|
/// The assignment wasn't valid. Only `true` or `false` is accepted.
|
||||||
|
#[error("Invalid assignment for `{identifier}`, expected `true` or `false`, got `{1}`", identifier = .0.identifier())]
|
||||||
|
InvalidAssignment(&'static ExperimentalOption, String),
|
||||||
|
|
||||||
|
/// This experimental option is deprecated as this is now the default behavior.
|
||||||
|
#[error("The experimental option `{identifier}` is deprecated as this is now the default behavior.", identifier = .0.identifier())]
|
||||||
|
DeprecatedDefault(&'static ExperimentalOption),
|
||||||
|
|
||||||
|
/// This experimental option is deprecated and will be removed in the future.
|
||||||
|
#[error("The experimental option `{identifier}` is deprecated and will be removed in a future release", identifier = .0.identifier())]
|
||||||
|
DeprecatedDiscard(&'static ExperimentalOption),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse and activate experimental options.
|
||||||
|
///
|
||||||
|
/// This is the recommended way to activate options, as it handles [`ParseWarning`]s properly
|
||||||
|
/// and is easy to hook into.
|
||||||
|
///
|
||||||
|
/// The `iter` argument should yield:
|
||||||
|
/// - the identifier of the option
|
||||||
|
/// - an optional assignment value (`true`/`false`)
|
||||||
|
/// - a context value, which is returned with any warning
|
||||||
|
///
|
||||||
|
/// This way you don't need to manually track which input caused which warning.
|
||||||
|
pub fn parse_iter<'i, Ctx>(
|
||||||
|
iter: impl Iterator<Item = (Cow<'i, str>, Option<Cow<'i, str>>, Ctx)>,
|
||||||
|
) -> Vec<(ParseWarning, Ctx)> {
|
||||||
|
let mut warnings = Vec::new();
|
||||||
|
for (key, val, ctx) in iter {
|
||||||
|
let Some(option) = ALL.iter().find(|option| option.identifier() == key.trim()) else {
|
||||||
|
warnings.push((ParseWarning::Unknown(key.to_string()), ctx));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
match option.status() {
|
||||||
|
Status::DeprecatedDiscard => {
|
||||||
|
warnings.push((ParseWarning::DeprecatedDiscard(option), ctx));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Status::DeprecatedDefault => {
|
||||||
|
warnings.push((ParseWarning::DeprecatedDefault(option), ctx));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = match val.as_deref().map(str::trim) {
|
||||||
|
None => true,
|
||||||
|
Some("true") => true,
|
||||||
|
Some("false") => false,
|
||||||
|
Some(s) => {
|
||||||
|
warnings.push((ParseWarning::InvalidAssignment(option, s.to_owned()), ctx));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option.value.store(val, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse experimental options from the [`ENV`] environment variable.
|
||||||
|
///
|
||||||
|
/// Uses [`parse_iter`] internally. Each warning includes a `Range<usize>` pointing to the
|
||||||
|
/// part of the environment variable that triggered it.
|
||||||
|
pub fn parse_env() -> Vec<(ParseWarning, Range<usize>)> {
|
||||||
|
let Ok(env) = env::var(ENV) else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
let mut start = 0;
|
||||||
|
for (idx, c) in env.char_indices() {
|
||||||
|
if c == ',' {
|
||||||
|
entries.push((&env[start..idx], start..idx));
|
||||||
|
start = idx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.push((&env[start..], start..env.len()));
|
||||||
|
|
||||||
|
parse_iter(entries.into_iter().map(|(entry, span)| {
|
||||||
|
entry
|
||||||
|
.split_once("=")
|
||||||
|
.map(|(key, val)| (key.into(), Some(val.into()), span.clone()))
|
||||||
|
.unwrap_or((entry.into(), None, span))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParseWarning {
|
||||||
|
/// A code to represent the variant.
|
||||||
|
///
|
||||||
|
/// This may be used with crates like [`miette`](https://docs.rs/miette) to provide error codes.
|
||||||
|
pub fn code(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Unknown(_) => "nu::experimental_option::unknown",
|
||||||
|
Self::InvalidAssignment(_, _) => "nu::experimental_option::invalid_assignment",
|
||||||
|
Self::DeprecatedDefault(_) => "nu::experimental_option::deprecated_default",
|
||||||
|
Self::DeprecatedDiscard(_) => "nu::experimental_option::deprecated_discard",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide some help depending on the variant.
|
||||||
|
///
|
||||||
|
/// This may be used with crates like [`miette`](https://docs.rs/miette) to provide a help
|
||||||
|
/// message.
|
||||||
|
pub fn help(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Unknown(_) => Some(format!(
|
||||||
|
"Known experimental options are: {}",
|
||||||
|
ALL.iter().map(|option| option.identifier()).join(", ")
|
||||||
|
)),
|
||||||
|
Self::InvalidAssignment(_, _) => None,
|
||||||
|
Self::DeprecatedDiscard(_) => None,
|
||||||
|
Self::DeprecatedDefault(_) => {
|
||||||
|
Some(String::from("You can safely remove this option now."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
crates/nu-experimental/src/util.rs
Normal file
44
crates/nu-experimental/src/util.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
|
|
||||||
|
/// Store an `Option<bool>` as an atomic.
|
||||||
|
///
|
||||||
|
/// # Implementation Detail
|
||||||
|
/// This stores the `Option<bool>` via its three states as `u8` representing:
|
||||||
|
/// - `None` as `0`
|
||||||
|
/// - `Some(true)` as `1`
|
||||||
|
/// - `Some(false)` as `2`
|
||||||
|
pub struct AtomicMaybe(AtomicU8);
|
||||||
|
|
||||||
|
impl AtomicMaybe {
|
||||||
|
pub const fn new(initial: Option<bool>) -> Self {
|
||||||
|
Self(AtomicU8::new(match initial {
|
||||||
|
None => 0,
|
||||||
|
Some(true) => 1,
|
||||||
|
Some(false) => 2,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(&self, value: impl Into<Option<bool>>, order: Ordering) {
|
||||||
|
self.0.store(
|
||||||
|
match value.into() {
|
||||||
|
None => 0,
|
||||||
|
Some(true) => 1,
|
||||||
|
Some(false) => 2,
|
||||||
|
},
|
||||||
|
order,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&self, order: Ordering) -> Option<bool> {
|
||||||
|
match self.0.load(order) {
|
||||||
|
0 => None,
|
||||||
|
1 => Some(true),
|
||||||
|
2 => Some(false),
|
||||||
|
_ => unreachable!("inner atomic is not exposed and can only set 0 to 2"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_ptr(&self) -> *mut u8 {
|
||||||
|
self.0.as_ptr()
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,11 @@ bench = false
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
nu-derive-value = { path = "../nu-derive-value", version = "0.105.2" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
nu-glob = { path = "../nu-glob", version = "0.105.2" }
|
||||||
nu-path = { path = "../nu-path", version = "0.105.2" }
|
nu-path = { path = "../nu-path", version = "0.105.2" }
|
||||||
nu-system = { path = "../nu-system", version = "0.105.2" }
|
nu-system = { path = "../nu-system", version = "0.105.2" }
|
||||||
nu-derive-value = { path = "../nu-derive-value", version = "0.105.2" }
|
nu-utils = { path = "../nu-utils", version = "0.105.2", default-features = false }
|
||||||
|
|
||||||
brotli = { workspace = true, optional = true }
|
brotli = { workspace = true, optional = true }
|
||||||
bytes = { workspace = true }
|
bytes = { workspace = true }
|
||||||
@ -38,7 +38,7 @@ serde = { workspace = true }
|
|||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
strum_macros = { workspace = true }
|
strum_macros = { workspace = true }
|
||||||
thiserror = "2.0.12"
|
thiserror = { workspace = true }
|
||||||
typetag = "0.2"
|
typetag = "0.2"
|
||||||
os_pipe = { workspace = true, optional = true, features = ["io_safety"] }
|
os_pipe = { workspace = true, optional = true, features = ["io_safety"] }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
@ -93,6 +93,13 @@ pub fn report_compile_error(working_set: &StateWorkingSet, error: &CompileError)
|
|||||||
report_error(working_set, error);
|
report_error(working_set, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_experimental_option_warning(
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
warning: &dyn miette::Diagnostic,
|
||||||
|
) {
|
||||||
|
report_warning(working_set, warning);
|
||||||
|
}
|
||||||
|
|
||||||
fn report_error(working_set: &StateWorkingSet, error: &dyn miette::Diagnostic) {
|
fn report_error(working_set: &StateWorkingSet, error: &dyn miette::Diagnostic) {
|
||||||
eprintln!("Error: {:?}", CliError(error, working_set));
|
eprintln!("Error: {:?}", CliError(error, working_set));
|
||||||
// reset vt processing, aka ansi because illbehaved externals can break it
|
// reset vt processing, aka ansi because illbehaved externals can break it
|
||||||
|
@ -35,9 +35,20 @@ pub(crate) fn gather_commandline_args() -> (Vec<String>, String, Vec<String>) {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
"--plugin-config" => args.next().map(|a| escape_quote_string(&a)),
|
||||||
"--log-level" | "--log-target" | "--log-include" | "--log-exclude" | "--testbin"
|
"--log-level"
|
||||||
| "--threads" | "-t" | "--include-path" | "--lsp" | "--ide-goto-def"
|
| "--log-target"
|
||||||
| "--ide-hover" | "--ide-complete" | "--ide-check" => args.next(),
|
| "--log-include"
|
||||||
|
| "--log-exclude"
|
||||||
|
| "--testbin"
|
||||||
|
| "--threads"
|
||||||
|
| "-t"
|
||||||
|
| "--include-path"
|
||||||
|
| "--lsp"
|
||||||
|
| "--ide-goto-def"
|
||||||
|
| "--ide-hover"
|
||||||
|
| "--ide-complete"
|
||||||
|
| "--ide-check"
|
||||||
|
| "--experimental-options" => args.next(),
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
"--plugins" => args.next(),
|
"--plugins" => args.next(),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -107,6 +118,7 @@ pub(crate) fn parse_commandline_args(
|
|||||||
let error_style: Option<Value> =
|
let error_style: Option<Value> =
|
||||||
call.get_flag(engine_state, &mut stack, "error-style")?;
|
call.get_flag(engine_state, &mut stack, "error-style")?;
|
||||||
let no_newline = call.get_named_arg("no-newline");
|
let no_newline = call.get_named_arg("no-newline");
|
||||||
|
let experimental_options = call.get_flag_expr("experimental-options");
|
||||||
|
|
||||||
// ide flags
|
// ide flags
|
||||||
let lsp = call.has_flag(engine_state, &mut stack, "lsp")?;
|
let lsp = call.has_flag(engine_state, &mut stack, "lsp")?;
|
||||||
@ -201,6 +213,8 @@ pub(crate) fn parse_commandline_args(
|
|||||||
let log_exclude = extract_list(log_exclude, "string", |expr| expr.as_string())?;
|
let log_exclude = extract_list(log_exclude, "string", |expr| expr.as_string())?;
|
||||||
let execute = extract_contents(execute)?;
|
let execute = extract_contents(execute)?;
|
||||||
let include_path = extract_contents(include_path)?;
|
let include_path = extract_contents(include_path)?;
|
||||||
|
let experimental_options =
|
||||||
|
extract_list(experimental_options, "string", |expr| expr.as_string())?;
|
||||||
|
|
||||||
let help = call.has_flag(engine_state, &mut stack, "help")?;
|
let help = call.has_flag(engine_state, &mut stack, "help")?;
|
||||||
|
|
||||||
@ -251,6 +265,7 @@ pub(crate) fn parse_commandline_args(
|
|||||||
table_mode,
|
table_mode,
|
||||||
error_style,
|
error_style,
|
||||||
no_newline,
|
no_newline,
|
||||||
|
experimental_options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -292,6 +307,7 @@ pub(crate) struct NushellCliArgs {
|
|||||||
pub(crate) ide_complete: Option<Value>,
|
pub(crate) ide_complete: Option<Value>,
|
||||||
pub(crate) ide_check: Option<Value>,
|
pub(crate) ide_check: Option<Value>,
|
||||||
pub(crate) ide_ast: Option<Spanned<String>>,
|
pub(crate) ide_ast: Option<Spanned<String>>,
|
||||||
|
pub(crate) experimental_options: Option<Vec<Spanned<String>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -452,6 +468,12 @@ impl Command for Nu {
|
|||||||
"run internal test binary",
|
"run internal test binary",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
.named(
|
||||||
|
"experimental-options",
|
||||||
|
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||||
|
"enable or disable experimental options",
|
||||||
|
None,
|
||||||
|
)
|
||||||
.optional(
|
.optional(
|
||||||
"script file",
|
"script file",
|
||||||
SyntaxShape::Filepath,
|
SyntaxShape::Filepath,
|
||||||
|
73
src/experimental_options.rs
Normal file
73
src/experimental_options.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
|
use nu_protocol::{
|
||||||
|
cli_error::report_experimental_option_warning,
|
||||||
|
engine::{EngineState, StateWorkingSet},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::command::NushellCliArgs;
|
||||||
|
|
||||||
|
// 1. Parse experimental options from env
|
||||||
|
// 2. See if we should have any and disable all of them if not
|
||||||
|
// 3. Parse CLI arguments, if explicitly mentioned, let's enable them
|
||||||
|
pub fn load(engine_state: &EngineState, cli_args: &NushellCliArgs, has_script: bool) {
|
||||||
|
let working_set = StateWorkingSet::new(engine_state);
|
||||||
|
|
||||||
|
if !should_disable_experimental_options(has_script, cli_args) {
|
||||||
|
let env_content = std::env::var(nu_experimental::ENV).unwrap_or_default();
|
||||||
|
let env_offset = format!("{}=", nu_experimental::ENV).len();
|
||||||
|
|
||||||
|
for (env_warning, span) in nu_experimental::parse_env() {
|
||||||
|
let span_offset = (span.start + env_offset)..(span.end + env_offset);
|
||||||
|
let mut diagnostic = miette::diagnostic!(
|
||||||
|
severity = miette::Severity::Warning,
|
||||||
|
code = env_warning.code(),
|
||||||
|
labels = vec![miette::LabeledSpan::new_with_span(None, span_offset)],
|
||||||
|
"{}",
|
||||||
|
env_warning,
|
||||||
|
);
|
||||||
|
if let Some(help) = env_warning.help() {
|
||||||
|
diagnostic = diagnostic.with_help(help);
|
||||||
|
}
|
||||||
|
|
||||||
|
let error = miette::Error::from(diagnostic).with_source_code(format!(
|
||||||
|
"{}={}",
|
||||||
|
nu_experimental::ENV,
|
||||||
|
env_content
|
||||||
|
));
|
||||||
|
report_experimental_option_warning(&working_set, error.borrow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (cli_arg_warning, ctx) in
|
||||||
|
nu_experimental::parse_iter(cli_args.experimental_options.iter().flatten().map(|entry| {
|
||||||
|
entry
|
||||||
|
.item
|
||||||
|
.split_once("=")
|
||||||
|
.map(|(key, val)| (key.into(), Some(val.into()), entry))
|
||||||
|
.unwrap_or((entry.item.clone().into(), None, entry))
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
let diagnostic = miette::diagnostic!(
|
||||||
|
severity = miette::Severity::Warning,
|
||||||
|
code = cli_arg_warning.code(),
|
||||||
|
labels = vec![miette::LabeledSpan::new_with_span(None, ctx.span)],
|
||||||
|
"{}",
|
||||||
|
cli_arg_warning,
|
||||||
|
);
|
||||||
|
match cli_arg_warning.help() {
|
||||||
|
Some(help) => {
|
||||||
|
report_experimental_option_warning(&working_set, &diagnostic.with_help(help))
|
||||||
|
}
|
||||||
|
None => report_experimental_option_warning(&working_set, &diagnostic),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_disable_experimental_options(has_script: bool, cli_args: &NushellCliArgs) -> bool {
|
||||||
|
has_script
|
||||||
|
|| cli_args.commands.is_some()
|
||||||
|
|| cli_args.execute.is_some()
|
||||||
|
|| cli_args.no_config_file.is_some()
|
||||||
|
|| cli_args.login_shell.is_some()
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
mod command;
|
mod command;
|
||||||
mod command_context;
|
mod command_context;
|
||||||
mod config_files;
|
mod config_files;
|
||||||
|
mod experimental_options;
|
||||||
mod ide;
|
mod ide;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod run;
|
mod run;
|
||||||
@ -201,6 +202,8 @@ fn main() -> Result<()> {
|
|||||||
std::process::exit(1)
|
std::process::exit(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
experimental_options::load(&engine_state, &parsed_nu_cli_args, !script_name.is_empty());
|
||||||
|
|
||||||
// keep this condition in sync with the branches at the end
|
// keep this condition in sync with the branches at the end
|
||||||
engine_state.is_interactive = parsed_nu_cli_args.interactive_shell.is_some()
|
engine_state.is_interactive = parsed_nu_cli_args.interactive_shell.is_some()
|
||||||
|| (parsed_nu_cli_args.testbin.is_none()
|
|| (parsed_nu_cli_args.testbin.is_none()
|
||||||
|
Reference in New Issue
Block a user