use crate::{ALL, ExperimentalOption, Status};
use itertools::Itertools;
use std::{borrow::Cow, env, ops::Range, sync::atomic::Ordering};
use thiserror::Error;
pub const ENV: &str = "NU_EXPERIMENTAL_OPTIONS";
#[derive(Debug, Clone, Error, Eq, PartialEq)]
pub enum ParseWarning {
#[error("Unknown experimental option `{0}`")]
Unknown(String),
#[error("Invalid assignment for `{identifier}`, expected `true` or `false`, got `{1}`", identifier = .0.identifier())]
InvalidAssignment(&'static ExperimentalOption, String),
#[error("Invalid assignment for `all`, expected `true` or `false`, got `{0}`")]
InvalidAssignmentAll(String),
#[error("The experimental option `{identifier}` is deprecated as this is now the default behavior.", identifier = .0.identifier())]
DeprecatedDefault(&'static ExperimentalOption),
#[error("The experimental option `{identifier}` is deprecated and will be removed in a future release", identifier = .0.identifier())]
DeprecatedDiscard(&'static ExperimentalOption),
}
pub fn parse_iter<'i, Ctx: Clone>(
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 {
if key == "all" {
let val = match parse_val(val.as_deref()) {
Ok(val) => val,
Err(s) => {
warnings.push((ParseWarning::InvalidAssignmentAll(s.to_owned()), ctx));
continue;
}
};
unsafe { super::set_all(val) };
continue;
}
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.clone()));
}
Status::DeprecatedDefault => {
warnings.push((ParseWarning::DeprecatedDefault(option), ctx.clone()));
}
_ => {}
}
let val = match parse_val(val.as_deref()) {
Ok(val) => val,
Err(s) => {
warnings.push((ParseWarning::InvalidAssignment(option, s.to_owned()), ctx));
continue;
}
};
option.value.store(val, Ordering::Relaxed);
}
warnings
}
fn parse_val(val: Option<&str>) -> Result<bool, &str> {
match val.map(str::trim) {
None => Ok(true),
Some("true") => Ok(true),
Some("false") => Ok(false),
Some(s) => Err(s),
}
}
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 {
pub fn code(&self) -> &'static str {
match self {
Self::Unknown(_) => "nu::experimental_option::unknown",
Self::InvalidAssignment(_, _) => "nu::experimental_option::invalid_assignment",
Self::InvalidAssignmentAll(_) => "nu::experimental_option::invalid_assignment_all",
Self::DeprecatedDefault(_) => "nu::experimental_option::deprecated_default",
Self::DeprecatedDiscard(_) => "nu::experimental_option::deprecated_discard",
}
}
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::InvalidAssignmentAll(_) => None,
Self::DeprecatedDiscard(_) => None,
Self::DeprecatedDefault(_) => {
Some(String::from("You can safely remove this option now."))
}
}
}
}