1use crate::{ALL, ExperimentalOption, Status};
2use itertools::Itertools;
3use std::{borrow::Cow, env, ops::Range, sync::atomic::Ordering};
4use thiserror::Error;
5
6pub const ENV: &str = "NU_EXPERIMENTAL_OPTIONS";
10
11#[derive(Debug, Clone, Error, Eq, PartialEq)]
13pub enum ParseWarning {
14 #[error("Unknown experimental option `{0}`")]
16 Unknown(String),
17
18 #[error("Invalid assignment for `{identifier}`, expected `true` or `false`, got `{1}`", identifier = .0.identifier())]
20 InvalidAssignment(&'static ExperimentalOption, String),
21
22 #[error("Invalid assignment for `all`, expected `true` or `false`, got `{0}`")]
24 InvalidAssignmentAll(String),
25
26 #[error("The experimental option `{identifier}` is deprecated as this is now the default behavior.", identifier = .0.identifier())]
28 DeprecatedDefault(&'static ExperimentalOption),
29
30 #[error("The experimental option `{identifier}` is deprecated and will be removed in a future release", identifier = .0.identifier())]
32 DeprecatedDiscard(&'static ExperimentalOption),
33}
34
35pub fn parse_iter<'i, Ctx: Clone>(
52 iter: impl Iterator<Item = (Cow<'i, str>, Option<Cow<'i, str>>, Ctx)>,
53) -> Vec<(ParseWarning, Ctx)> {
54 let mut warnings = Vec::new();
55 for (key, val, ctx) in iter {
56 if key == "all" {
57 let val = match parse_val(val.as_deref()) {
58 Ok(val) => val,
59 Err(s) => {
60 warnings.push((ParseWarning::InvalidAssignmentAll(s.to_owned()), ctx));
61 continue;
62 }
63 };
64 unsafe { super::set_all(val) };
66 continue;
67 }
68
69 let Some(option) = ALL.iter().find(|option| option.identifier() == key.trim()) else {
70 warnings.push((ParseWarning::Unknown(key.to_string()), ctx));
71 continue;
72 };
73
74 match option.status() {
75 Status::DeprecatedDiscard => {
76 warnings.push((ParseWarning::DeprecatedDiscard(option), ctx.clone()));
77 }
78 Status::DeprecatedDefault => {
79 warnings.push((ParseWarning::DeprecatedDefault(option), ctx.clone()));
80 }
81 _ => {}
82 }
83
84 let val = match parse_val(val.as_deref()) {
85 Ok(val) => val,
86 Err(s) => {
87 warnings.push((ParseWarning::InvalidAssignment(option, s.to_owned()), ctx));
88 continue;
89 }
90 };
91
92 option.value.store(val, Ordering::Relaxed);
93 }
94
95 warnings
96}
97
98fn parse_val(val: Option<&str>) -> Result<bool, &str> {
99 match val.map(str::trim) {
100 None => Ok(true),
101 Some("true") => Ok(true),
102 Some("false") => Ok(false),
103 Some(s) => Err(s),
104 }
105}
106
107pub fn parse_env() -> Vec<(ParseWarning, Range<usize>)> {
112 let Ok(env) = env::var(ENV) else {
113 return vec![];
114 };
115
116 let mut entries = Vec::new();
117 let mut start = 0;
118 for (idx, c) in env.char_indices() {
119 if c == ',' {
120 entries.push((&env[start..idx], start..idx));
121 start = idx + 1;
122 }
123 }
124 entries.push((&env[start..], start..env.len()));
125
126 parse_iter(entries.into_iter().map(|(entry, span)| {
127 entry
128 .split_once("=")
129 .map(|(key, val)| (key.into(), Some(val.into()), span.clone()))
130 .unwrap_or((entry.into(), None, span))
131 }))
132}
133
134impl ParseWarning {
135 pub fn code(&self) -> &'static str {
139 match self {
140 Self::Unknown(_) => "nu::experimental_option::unknown",
141 Self::InvalidAssignment(_, _) => "nu::experimental_option::invalid_assignment",
142 Self::InvalidAssignmentAll(_) => "nu::experimental_option::invalid_assignment_all",
143 Self::DeprecatedDefault(_) => "nu::experimental_option::deprecated_default",
144 Self::DeprecatedDiscard(_) => "nu::experimental_option::deprecated_discard",
145 }
146 }
147
148 pub fn help(&self) -> Option<String> {
153 match self {
154 Self::Unknown(_) => Some(format!(
155 "Known experimental options are: {}",
156 ALL.iter().map(|option| option.identifier()).join(", ")
157 )),
158 Self::InvalidAssignment(_, _) => None,
159 Self::InvalidAssignmentAll(_) => None,
160 Self::DeprecatedDiscard(_) => None,
161 Self::DeprecatedDefault(_) => {
162 Some(String::from("You can safely remove this option now."))
163 }
164 }
165 }
166}