brush_core/
variables.rs

1//! Implements variables for a shell environment.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::fmt::{Display, Write};
6
7use crate::shell::Shell;
8use crate::{error, escape};
9
10/// A shell variable.
11#[derive(Clone, Debug)]
12pub struct ShellVariable {
13    /// The value currently associated with the variable.
14    value: ShellValue,
15    /// Whether or not the variable is marked as exported to child processes.
16    exported: bool,
17    /// Whether or not the variable is marked as read-only.
18    readonly: bool,
19    /// Whether or not the variable should be enumerated in the shell's environment.
20    enumerable: bool,
21    /// The transformation to apply to the variable's value when it is updated.
22    transform_on_update: ShellVariableUpdateTransform,
23    /// Whether or not the variable is marked as being traced.
24    trace: bool,
25    /// Whether or not the variable should be treated as an integer.
26    treat_as_integer: bool,
27    /// Whether or not the variable should be treated as a name reference.
28    treat_as_nameref: bool,
29}
30
31/// Kind of transformation to apply to a variable's value when it is updated.
32#[derive(Clone, Copy, Debug)]
33pub enum ShellVariableUpdateTransform {
34    /// No transformation.
35    None,
36    /// Convert the value to lowercase.
37    Lowercase,
38    /// Convert the value to uppercase.
39    Uppercase,
40    /// Convert the value to lowercase, with the first character capitalized.
41    Capitalize,
42}
43
44impl Default for ShellVariable {
45    fn default() -> Self {
46        Self {
47            value: ShellValue::String(String::new()),
48            exported: false,
49            readonly: false,
50            enumerable: true,
51            transform_on_update: ShellVariableUpdateTransform::None,
52            trace: false,
53            treat_as_integer: false,
54            treat_as_nameref: false,
55        }
56    }
57}
58
59impl ShellVariable {
60    /// Returns a new shell variable, initialized with the given value.
61    ///
62    /// # Arguments
63    ///
64    /// * `value` - The value to associate with the variable.
65    pub fn new(value: ShellValue) -> Self {
66        Self {
67            value,
68            ..Self::default()
69        }
70    }
71
72    /// Returns the value associated with the variable.
73    pub const fn value(&self) -> &ShellValue {
74        &self.value
75    }
76
77    /// Returns whether or not the variable is exported to child processes.
78    pub const fn is_exported(&self) -> bool {
79        self.exported
80    }
81
82    /// Marks the variable as exported to child processes.
83    pub const fn export(&mut self) -> &mut Self {
84        self.exported = true;
85        self
86    }
87
88    /// Marks the variable as not exported to child processes.
89    pub const fn unexport(&mut self) -> &mut Self {
90        self.exported = false;
91        self
92    }
93
94    /// Returns whether or not the variable is read-only.
95    pub const fn is_readonly(&self) -> bool {
96        self.readonly
97    }
98
99    /// Marks the variable as read-only.
100    pub const fn set_readonly(&mut self) -> &mut Self {
101        self.readonly = true;
102        self
103    }
104
105    /// Marks the variable as not read-only.
106    pub const fn unset_readonly(&mut self) -> Result<&mut Self, error::Error> {
107        if self.readonly {
108            return Err(error::Error::ReadonlyVariable);
109        }
110
111        self.readonly = false;
112        Ok(self)
113    }
114
115    /// Returns whether or not the variable is traced.
116    pub const fn is_trace_enabled(&self) -> bool {
117        self.trace
118    }
119
120    /// Marks the variable as traced.
121    pub const fn enable_trace(&mut self) -> &mut Self {
122        self.trace = true;
123        self
124    }
125
126    /// Marks the variable as not traced.
127    pub const fn disable_trace(&mut self) -> &mut Self {
128        self.trace = false;
129        self
130    }
131
132    /// Returns whether or not the variable should be enumerated in the shell's environment.
133    pub const fn is_enumerable(&self) -> bool {
134        self.enumerable
135    }
136
137    /// Marks the variable as not enumerable in the shell's environment.
138    pub const fn hide_from_enumeration(&mut self) -> &mut Self {
139        self.enumerable = false;
140        self
141    }
142
143    /// Return the update transform associated with the variable.
144    pub const fn get_update_transform(&self) -> ShellVariableUpdateTransform {
145        self.transform_on_update
146    }
147
148    /// Set the update transform associated with the variable.
149    pub const fn set_update_transform(&mut self, transform: ShellVariableUpdateTransform) {
150        self.transform_on_update = transform;
151    }
152
153    /// Returns whether or not the variable should be treated as an integer.
154    pub const fn is_treated_as_integer(&self) -> bool {
155        self.treat_as_integer
156    }
157
158    /// Marks the variable as being treated as an integer.
159    pub const fn treat_as_integer(&mut self) -> &mut Self {
160        self.treat_as_integer = true;
161        self
162    }
163
164    /// Marks the variable as not being treated as an integer.
165    pub const fn unset_treat_as_integer(&mut self) -> &mut Self {
166        self.treat_as_integer = false;
167        self
168    }
169
170    /// Returns whether or not the variable should be treated as a name reference.
171    pub const fn is_treated_as_nameref(&self) -> bool {
172        self.treat_as_nameref
173    }
174
175    /// Marks the variable as being treated as a name reference.
176    pub const fn treat_as_nameref(&mut self) -> &mut Self {
177        self.treat_as_nameref = true;
178        self
179    }
180
181    /// Marks the variable as not being treated as a name reference.
182    pub const fn unset_treat_as_nameref(&mut self) -> &mut Self {
183        self.treat_as_nameref = false;
184        self
185    }
186
187    /// Converts the variable to an indexed array.
188    pub fn convert_to_indexed_array(&mut self) -> Result<(), error::Error> {
189        match self.value() {
190            ShellValue::IndexedArray(_) => Ok(()),
191            ShellValue::AssociativeArray(_) => {
192                Err(error::Error::ConvertingAssociativeArrayToIndexedArray)
193            }
194            _ => {
195                let mut new_values = BTreeMap::new();
196                new_values.insert(
197                    0,
198                    self.value.to_cow_str_without_dynamic_support().to_string(),
199                );
200                self.value = ShellValue::IndexedArray(new_values);
201                Ok(())
202            }
203        }
204    }
205
206    /// Converts the variable to an associative array.
207    pub fn convert_to_associative_array(&mut self) -> Result<(), error::Error> {
208        match self.value() {
209            ShellValue::AssociativeArray(_) => Ok(()),
210            ShellValue::IndexedArray(_) => {
211                Err(error::Error::ConvertingIndexedArrayToAssociativeArray)
212            }
213            _ => {
214                let mut new_values: BTreeMap<String, String> = BTreeMap::new();
215                new_values.insert(
216                    String::from("0"),
217                    self.value.to_cow_str_without_dynamic_support().to_string(),
218                );
219                self.value = ShellValue::AssociativeArray(new_values);
220                Ok(())
221            }
222        }
223    }
224
225    /// Assign the given value to the variable, conditionally appending to the preexisting value.
226    ///
227    /// # Arguments
228    ///
229    /// * `value` - The value to assign to the variable.
230    /// * `append` - Whether or not to append the value to the preexisting value.
231    #[allow(clippy::too_many_lines)]
232    pub fn assign(&mut self, value: ShellValueLiteral, append: bool) -> Result<(), error::Error> {
233        if self.is_readonly() {
234            return Err(error::Error::ReadonlyVariable);
235        }
236
237        let value = self.convert_value_literal_for_assignment(value);
238
239        if append {
240            match (&self.value, &value) {
241                // If we're appending an array to a declared-but-unset variable (or appending
242                // anything to a declared-but-unset array), then fill it out first.
243                (ShellValue::Unset(_), ShellValueLiteral::Array(_))
244                | (
245                    ShellValue::Unset(
246                        ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray,
247                    ),
248                    _,
249                ) => {
250                    self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
251                }
252                // If we're appending a scalar to a declared-but-unset variable, then
253                // start with the empty string. This will result in the right thing happening,
254                // even in treat-as-integer cases.
255                (ShellValue::Unset(_), ShellValueLiteral::Scalar(_)) => {
256                    self.assign(ShellValueLiteral::Scalar(String::new()), false)?;
257                }
258                // If we're trying to append an array to a string, we first promote the string to be
259                // an array with the string being present at index 0.
260                (ShellValue::String(_), ShellValueLiteral::Array(_)) => {
261                    self.convert_to_indexed_array()?;
262                }
263                _ => (),
264            }
265
266            let treat_as_int = self.is_treated_as_integer();
267            let update_transform = self.get_update_transform();
268
269            match &mut self.value {
270                ShellValue::String(base) => match value {
271                    ShellValueLiteral::Scalar(suffix) => {
272                        if treat_as_int {
273                            let int_value = base.parse::<i64>().unwrap_or(0)
274                                + suffix.parse::<i64>().unwrap_or(0);
275                            base.clear();
276                            base.push_str(int_value.to_string().as_str());
277                        } else {
278                            base.push_str(suffix.as_str());
279                            Self::apply_value_transforms(base, treat_as_int, update_transform);
280                        }
281                        Ok(())
282                    }
283                    ShellValueLiteral::Array(_) => {
284                        // This case was already handled (see above).
285                        Ok(())
286                    }
287                },
288                ShellValue::IndexedArray(existing_values) => match value {
289                    ShellValueLiteral::Scalar(new_value) => {
290                        self.assign_at_index(String::from("0"), new_value, append)
291                    }
292                    ShellValueLiteral::Array(new_values) => {
293                        ShellValue::update_indexed_array_from_literals(existing_values, new_values);
294                        Ok(())
295                    }
296                },
297                ShellValue::AssociativeArray(existing_values) => match value {
298                    ShellValueLiteral::Scalar(new_value) => {
299                        self.assign_at_index(String::from("0"), new_value, append)
300                    }
301                    ShellValueLiteral::Array(new_values) => {
302                        ShellValue::update_associative_array_from_literals(
303                            existing_values,
304                            new_values,
305                        )
306                    }
307                },
308                ShellValue::Unset(_) => unreachable!("covered in conversion above"),
309                // TODO(dynamic): implement appending to dynamic vars
310                ShellValue::Dynamic { .. } => Ok(()),
311            }
312        } else {
313            match (&self.value, value) {
314                // If we're updating an array value with a string, then treat it as an update to
315                // just the "0"-indexed element of the array.
316                (
317                    ShellValue::IndexedArray(_)
318                    | ShellValue::AssociativeArray(_)
319                    | ShellValue::Unset(
320                        ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray,
321                    ),
322                    ShellValueLiteral::Scalar(s),
323                ) => self.assign_at_index(String::from("0"), s, false),
324
325                // If we're updating an indexed array value with an array, then preserve the array
326                // type. We also default to using an indexed array if we are
327                // assigning an array to a previously string-holding variable.
328                (
329                    ShellValue::IndexedArray(_)
330                    | ShellValue::Unset(
331                        ShellValueUnsetType::IndexedArray | ShellValueUnsetType::Untyped,
332                    )
333                    | ShellValue::String(_)
334                    | ShellValue::Dynamic { .. },
335                    ShellValueLiteral::Array(literal_values),
336                ) => {
337                    self.value = ShellValue::indexed_array_from_literals(literal_values);
338                    Ok(())
339                }
340
341                // If we're updating an associative array value with an array, then preserve the
342                // array type.
343                (
344                    ShellValue::AssociativeArray(_)
345                    | ShellValue::Unset(ShellValueUnsetType::AssociativeArray),
346                    ShellValueLiteral::Array(literal_values),
347                ) => {
348                    self.value = ShellValue::associative_array_from_literals(literal_values)?;
349                    Ok(())
350                }
351
352                // Handle updates to dynamic values; for now we just drop them.
353                // TODO(dynamic): Allow updates to dynamic values
354                (ShellValue::Dynamic { .. }, _) => Ok(()),
355
356                // Assign a scalar value to a scalar or unset (and untyped) variable.
357                (ShellValue::String(_) | ShellValue::Unset(_), ShellValueLiteral::Scalar(s)) => {
358                    self.value = ShellValue::String(s);
359                    Ok(())
360                }
361            }
362        }
363    }
364
365    /// Assign the given value to the variable at the given index, conditionally appending to the
366    /// preexisting value present at that element within the value.
367    ///
368    /// # Arguments
369    ///
370    /// * `array_index` - The index at which to assign the value.
371    /// * `value` - The value to assign to the variable at the given index.
372    /// * `append` - Whether or not to append the value to the preexisting value stored at the given
373    ///   index.
374    #[allow(clippy::needless_pass_by_value)]
375    pub fn assign_at_index(
376        &mut self,
377        array_index: String,
378        value: String,
379        append: bool,
380    ) -> Result<(), error::Error> {
381        match &self.value {
382            ShellValue::Unset(_) => {
383                self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
384            }
385            ShellValue::String(_) => {
386                self.convert_to_indexed_array()?;
387            }
388            _ => (),
389        }
390
391        let treat_as_int = self.is_treated_as_integer();
392        let value = self.convert_value_str_for_assignment(value);
393
394        match &mut self.value {
395            ShellValue::IndexedArray(arr) => {
396                let key: u64 = array_index.parse().unwrap_or(0);
397
398                if append {
399                    let existing_value = arr.get(&key).map_or_else(|| "", |v| v.as_str());
400
401                    let mut new_value;
402                    if treat_as_int {
403                        new_value = (existing_value.parse::<i64>().unwrap_or(0)
404                            + value.parse::<i64>().unwrap_or(0))
405                        .to_string();
406                    } else {
407                        new_value = existing_value.to_owned();
408                        new_value.push_str(value.as_str());
409                    }
410
411                    arr.insert(key, new_value);
412                } else {
413                    arr.insert(key, value);
414                }
415
416                Ok(())
417            }
418            ShellValue::AssociativeArray(arr) => {
419                if append {
420                    let existing_value = arr
421                        .get(array_index.as_str())
422                        .map_or_else(|| "", |v| v.as_str());
423
424                    let mut new_value;
425                    if treat_as_int {
426                        new_value = (existing_value.parse::<i64>().unwrap_or(0)
427                            + value.parse::<i64>().unwrap_or(0))
428                        .to_string();
429                    } else {
430                        new_value = existing_value.to_owned();
431                        new_value.push_str(value.as_str());
432                    }
433
434                    arr.insert(array_index, new_value.to_string());
435                } else {
436                    arr.insert(array_index, value);
437                }
438                Ok(())
439            }
440            _ => {
441                tracing::error!("assigning to index {array_index} of {:?}", self.value);
442                error::unimp("assigning to index of non-array variable")
443            }
444        }
445    }
446
447    fn convert_value_literal_for_assignment(&self, value: ShellValueLiteral) -> ShellValueLiteral {
448        match value {
449            ShellValueLiteral::Scalar(s) => {
450                ShellValueLiteral::Scalar(self.convert_value_str_for_assignment(s))
451            }
452            ShellValueLiteral::Array(literals) => ShellValueLiteral::Array(ArrayLiteral(
453                literals
454                    .0
455                    .into_iter()
456                    .map(|(k, v)| (k, self.convert_value_str_for_assignment(v)))
457                    .collect(),
458            )),
459        }
460    }
461
462    fn convert_value_str_for_assignment(&self, mut s: String) -> String {
463        Self::apply_value_transforms(
464            &mut s,
465            self.is_treated_as_integer(),
466            self.get_update_transform(),
467        );
468
469        s
470    }
471
472    fn apply_value_transforms(
473        s: &mut String,
474        treat_as_int: bool,
475        update_transform: ShellVariableUpdateTransform,
476    ) {
477        if treat_as_int {
478            *s = (*s).parse::<i64>().unwrap_or(0).to_string();
479        } else {
480            match update_transform {
481                ShellVariableUpdateTransform::None => (),
482                ShellVariableUpdateTransform::Lowercase => *s = (*s).to_lowercase(),
483                ShellVariableUpdateTransform::Uppercase => *s = (*s).to_uppercase(),
484                ShellVariableUpdateTransform::Capitalize => {
485                    // This isn't really title-case; only the first character is capitalized.
486                    *s = s.to_lowercase();
487                    if let Some(c) = s.chars().next() {
488                        s.replace_range(0..1, &c.to_uppercase().to_string());
489                    }
490                }
491            }
492        }
493    }
494
495    /// Tries to unset the value stored at the given index in the variable. Returns
496    /// whether or not a value was unset.
497    ///
498    /// # Arguments
499    ///
500    /// * `index` - The index at which to unset the value.
501    pub fn unset_index(&mut self, index: &str) -> Result<bool, error::Error> {
502        match &mut self.value {
503            ShellValue::Unset(ty) => match ty {
504                ShellValueUnsetType::Untyped => Err(error::Error::NotArray),
505                ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray => {
506                    Ok(false)
507                }
508            },
509            ShellValue::String(_) => Err(error::Error::NotArray),
510            ShellValue::AssociativeArray(values) => Ok(values.remove(index).is_some()),
511            ShellValue::IndexedArray(values) => {
512                let key = index.parse::<u64>().unwrap_or(0);
513                Ok(values.remove(&key).is_some())
514            }
515            ShellValue::Dynamic { .. } => Ok(false),
516        }
517    }
518
519    /// Returns the variable's value; for dynamic values, this will resolve the value.
520    ///
521    /// # Arguments
522    ///
523    /// * `shell` - The shell in which the variable is being resolved.
524    pub(crate) fn resolve_value(&self, shell: &Shell) -> ShellValue {
525        // N.B. We do *not* specially handle a dynamic value that resolves to a dynamic value.
526        match &self.value {
527            ShellValue::Dynamic { getter, .. } => getter(shell),
528            _ => self.value.clone(),
529        }
530    }
531
532    /// Returns the canonical attribute flag string for this variable.
533    pub fn get_attribute_flags(&self, shell: &Shell) -> String {
534        let value = self.resolve_value(shell);
535
536        let mut result = String::new();
537
538        if matches!(
539            value,
540            ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
541        ) {
542            result.push('a');
543        }
544        if matches!(
545            value,
546            ShellValue::AssociativeArray(_)
547                | ShellValue::Unset(ShellValueUnsetType::AssociativeArray)
548        ) {
549            result.push('A');
550        }
551        if matches!(
552            self.get_update_transform(),
553            ShellVariableUpdateTransform::Capitalize
554        ) {
555            result.push('c');
556        }
557        if self.is_treated_as_integer() {
558            result.push('i');
559        }
560        if self.is_treated_as_nameref() {
561            result.push('n');
562        }
563        if self.is_readonly() {
564            result.push('r');
565        }
566        if matches!(
567            self.get_update_transform(),
568            ShellVariableUpdateTransform::Lowercase
569        ) {
570            result.push('l');
571        }
572        if self.is_trace_enabled() {
573            result.push('t');
574        }
575        if matches!(
576            self.get_update_transform(),
577            ShellVariableUpdateTransform::Uppercase
578        ) {
579            result.push('u');
580        }
581        if self.is_exported() {
582            result.push('x');
583        }
584
585        result
586    }
587}
588
589type DynamicValueGetter = fn(&Shell) -> ShellValue;
590type DynamicValueSetter = fn(&Shell) -> ();
591
592/// A shell value.
593#[derive(Clone, Debug)]
594pub enum ShellValue {
595    /// A value that has been typed but not yet set.
596    Unset(ShellValueUnsetType),
597    /// A string.
598    String(String),
599    /// An associative array.
600    AssociativeArray(BTreeMap<String, String>),
601    /// An indexed array.
602    IndexedArray(BTreeMap<u64, String>),
603    /// A value that is dynamically computed.
604    Dynamic {
605        /// Function that can query the value.
606        getter: DynamicValueGetter,
607        /// Function that receives value update requests.
608        setter: DynamicValueSetter,
609    },
610}
611
612/// The type of an unset shell value.
613#[derive(Clone, Debug)]
614pub enum ShellValueUnsetType {
615    /// The value is untyped.
616    Untyped,
617    /// The value is an associative array.
618    AssociativeArray,
619    /// The value is an indexed array.
620    IndexedArray,
621}
622
623/// A shell value literal; used for assignment.
624#[derive(Clone, Debug)]
625pub enum ShellValueLiteral {
626    /// A scalar value.
627    Scalar(String),
628    /// An array value.
629    Array(ArrayLiteral),
630}
631
632impl ShellValueLiteral {
633    pub(crate) fn fmt_for_tracing(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
634        match self {
635            Self::Scalar(s) => Self::fmt_scalar_for_tracing(s.as_str(), f),
636            Self::Array(elements) => {
637                write!(f, "(")?;
638                for (i, (key, value)) in elements.0.iter().enumerate() {
639                    if i > 0 {
640                        write!(f, " ")?;
641                    }
642                    if let Some(key) = key {
643                        write!(f, "[")?;
644                        Self::fmt_scalar_for_tracing(key.as_str(), f)?;
645                        write!(f, "]=")?;
646                    }
647                    Self::fmt_scalar_for_tracing(value.as_str(), f)?;
648                }
649                write!(f, ")")
650            }
651        }
652    }
653
654    fn fmt_scalar_for_tracing(s: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655        let processed = escape::quote_if_needed(s, escape::QuoteMode::SingleQuote);
656        write!(f, "{processed}")
657    }
658}
659
660impl Display for ShellValueLiteral {
661    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
662        self.fmt_for_tracing(f)
663    }
664}
665
666impl From<&str> for ShellValueLiteral {
667    fn from(value: &str) -> Self {
668        Self::Scalar(value.to_owned())
669    }
670}
671
672impl From<String> for ShellValueLiteral {
673    fn from(value: String) -> Self {
674        Self::Scalar(value)
675    }
676}
677
678impl From<Vec<&str>> for ShellValueLiteral {
679    fn from(value: Vec<&str>) -> Self {
680        Self::Array(ArrayLiteral(
681            value.into_iter().map(|s| (None, s.to_owned())).collect(),
682        ))
683    }
684}
685
686/// An array literal.
687#[derive(Clone, Debug)]
688pub struct ArrayLiteral(pub Vec<(Option<String>, String)>);
689
690/// Style for formatting a shell variable's value.
691#[derive(Copy, Clone, Debug)]
692pub enum FormatStyle {
693    /// Basic formatting.
694    Basic,
695    /// Formatting as appropriate in the `declare` built-in command.
696    DeclarePrint,
697}
698
699impl ShellValue {
700    /// Returns whether or not the value is an array.
701    pub const fn is_array(&self) -> bool {
702        matches!(
703            self,
704            Self::IndexedArray(_)
705                | Self::AssociativeArray(_)
706                | Self::Unset(
707                    ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray
708                )
709        )
710    }
711
712    /// Returns whether or not the value is set.
713    pub const fn is_set(&self) -> bool {
714        !matches!(self, Self::Unset(_))
715    }
716
717    /// Returns a new indexed array value constructed from the given slice of owned strings.
718    ///
719    /// # Arguments
720    ///
721    /// * `values` - The slice of strings to construct the indexed array from.
722    pub fn indexed_array_from_strings<S>(values: S) -> Self
723    where
724        S: IntoIterator<Item = String>,
725    {
726        let mut owned_values = BTreeMap::new();
727        for (i, value) in values.into_iter().enumerate() {
728            owned_values.insert(i as u64, value);
729        }
730
731        Self::IndexedArray(owned_values)
732    }
733
734    /// Returns a new indexed array value constructed from the given slice of unowned strings.
735    ///
736    /// # Arguments
737    ///
738    /// * `values` - The slice of strings to construct the indexed array from.
739    pub fn indexed_array_from_strs(values: &[&str]) -> Self {
740        let mut owned_values = BTreeMap::new();
741        for (i, value) in values.iter().enumerate() {
742            owned_values.insert(i as u64, (*value).to_string());
743        }
744
745        Self::IndexedArray(owned_values)
746    }
747
748    /// Returns a new indexed array value constructed from the given literals.
749    ///
750    /// # Arguments
751    ///
752    /// * `literals` - The literals to construct the indexed array from.
753    pub fn indexed_array_from_literals(literals: ArrayLiteral) -> Self {
754        let mut values = BTreeMap::new();
755        Self::update_indexed_array_from_literals(&mut values, literals);
756
757        Self::IndexedArray(values)
758    }
759
760    fn update_indexed_array_from_literals(
761        existing_values: &mut BTreeMap<u64, String>,
762        literal_values: ArrayLiteral,
763    ) {
764        let mut new_key = if let Some((largest_index, _)) = existing_values.last_key_value() {
765            largest_index + 1
766        } else {
767            0
768        };
769
770        for (key, value) in literal_values.0 {
771            if let Some(key) = key {
772                new_key = key.parse().unwrap_or(0);
773            }
774
775            existing_values.insert(new_key, value);
776            new_key += 1;
777        }
778    }
779
780    /// Returns a new associative array value constructed from the given literals.
781    ///
782    /// # Arguments
783    ///
784    /// * `literals` - The literals to construct the associative array from.
785    pub fn associative_array_from_literals(literals: ArrayLiteral) -> Result<Self, error::Error> {
786        let mut values = BTreeMap::new();
787        Self::update_associative_array_from_literals(&mut values, literals)?;
788
789        Ok(Self::AssociativeArray(values))
790    }
791
792    fn update_associative_array_from_literals(
793        existing_values: &mut BTreeMap<String, String>,
794        literal_values: ArrayLiteral,
795    ) -> Result<(), error::Error> {
796        let mut current_key = None;
797        for (key, value) in literal_values.0 {
798            if let Some(current_key) = current_key.take() {
799                if key.is_some() {
800                    return error::unimp("misaligned keys/values in associative array literal");
801                } else {
802                    existing_values.insert(current_key, value);
803                }
804            } else if let Some(key) = key {
805                existing_values.insert(key, value);
806            } else {
807                current_key = Some(value);
808            }
809        }
810
811        if let Some(current_key) = current_key {
812            existing_values.insert(current_key, String::new());
813        }
814
815        Ok(())
816    }
817
818    /// Formats the value using the given style.
819    ///
820    /// # Arguments
821    ///
822    /// * `style` - The style to use for formatting the value.
823    pub fn format(&self, style: FormatStyle, shell: &Shell) -> Result<Cow<'_, str>, error::Error> {
824        match self {
825            Self::Unset(_) => Ok("".into()),
826            Self::String(s) => match style {
827                FormatStyle::Basic => Ok(escape::quote_if_needed(
828                    s.as_str(),
829                    escape::QuoteMode::SingleQuote,
830                )),
831                FormatStyle::DeclarePrint => {
832                    Ok(escape::force_quote(s.as_str(), escape::QuoteMode::DoubleQuote).into())
833                }
834            },
835            Self::AssociativeArray(values) => {
836                let mut result = String::new();
837                result.push('(');
838
839                for (key, value) in values {
840                    let formatted_key =
841                        escape::quote_if_needed(key.as_str(), escape::QuoteMode::DoubleQuote);
842                    let formatted_value =
843                        escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
844
845                    // N.B. We include an unconditional trailing space character (even after the
846                    // last entry in the associative array) to match standard
847                    // output behavior.
848                    write!(result, "[{formatted_key}]={formatted_value} ")?;
849                }
850
851                result.push(')');
852                Ok(result.into())
853            }
854            Self::IndexedArray(values) => {
855                let mut result = String::new();
856                result.push('(');
857
858                for (i, (key, value)) in values.iter().enumerate() {
859                    if i > 0 {
860                        result.push(' ');
861                    }
862
863                    let formatted_value =
864                        escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
865                    write!(result, "[{key}]={formatted_value}")?;
866                }
867
868                result.push(')');
869                Ok(result.into())
870            }
871            Self::Dynamic { getter, .. } => {
872                let dynamic_value = getter(shell);
873                let result = dynamic_value.format(style, shell)?.to_string();
874                Ok(result.into())
875            }
876        }
877    }
878
879    /// Tries to retrieve the value stored at the given index in this variable.
880    ///
881    /// # Arguments
882    ///
883    /// * `index` - The index at which to retrieve the value.
884    pub fn get_at(&self, index: &str, shell: &Shell) -> Result<Option<Cow<'_, str>>, error::Error> {
885        match self {
886            Self::Unset(_) => Ok(None),
887            Self::String(s) => {
888                if index.parse::<u64>().unwrap_or(0) == 0 {
889                    Ok(Some(Cow::Borrowed(s)))
890                } else {
891                    Ok(None)
892                }
893            }
894            Self::AssociativeArray(values) => {
895                Ok(values.get(index).map(|s| Cow::Borrowed(s.as_str())))
896            }
897            Self::IndexedArray(values) => {
898                let mut index_value = index.parse::<i64>().unwrap_or(0);
899
900                #[allow(clippy::cast_possible_wrap)]
901                if index_value < 0 {
902                    index_value += values.len() as i64;
903                    if index_value < 0 {
904                        return Err(error::Error::ArrayIndexOutOfRange);
905                    }
906                }
907
908                // Now that we've confirmed that the index is non-negative, we can safely convert it
909                // to a u64 without any fuss.
910                #[allow(clippy::cast_sign_loss)]
911                let index_value = index_value as u64;
912
913                Ok(values.get(&index_value).map(|s| Cow::Borrowed(s.as_str())))
914            }
915            Self::Dynamic { getter, .. } => {
916                let dynamic_value = getter(shell);
917                let result = dynamic_value.get_at(index, shell)?;
918                Ok(result.map(|s| s.to_string().into()))
919            }
920        }
921    }
922
923    /// Returns the keys of the elements in this variable.
924    pub fn get_element_keys(&self, shell: &Shell) -> Vec<String> {
925        match self {
926            Self::Unset(_) => vec![],
927            Self::String(_) => vec!["0".to_owned()],
928            Self::AssociativeArray(array) => array.keys().map(|k| k.to_owned()).collect(),
929            Self::IndexedArray(array) => array.keys().map(|k| k.to_string()).collect(),
930            Self::Dynamic { getter, .. } => getter(shell).get_element_keys(shell),
931        }
932    }
933
934    /// Returns the values of the elements in this variable.
935    pub fn get_element_values(&self, shell: &Shell) -> Vec<String> {
936        match self {
937            Self::Unset(_) => vec![],
938            Self::String(s) => vec![s.to_owned()],
939            Self::AssociativeArray(array) => array.values().map(|v| v.to_owned()).collect(),
940            Self::IndexedArray(array) => array.values().map(|v| v.to_owned()).collect(),
941            Self::Dynamic { getter, .. } => getter(shell).get_element_values(shell),
942        }
943    }
944
945    /// Converts this value to a string.
946    pub fn to_cow_str(&self, shell: &Shell) -> Cow<'_, str> {
947        self.try_get_cow_str(shell).unwrap_or(Cow::Borrowed(""))
948    }
949
950    fn to_cow_str_without_dynamic_support(&self) -> Cow<'_, str> {
951        self.try_get_cow_str_without_dynamic_support()
952            .unwrap_or(Cow::Borrowed(""))
953    }
954
955    /// Tries to convert this value to a string; returns `None` if the value is unset
956    /// or otherwise doesn't exist.
957    pub fn try_get_cow_str(&self, shell: &Shell) -> Option<Cow<'_, str>> {
958        match self {
959            Self::Dynamic { getter, .. } => {
960                let dynamic_value = getter(shell);
961                dynamic_value
962                    .try_get_cow_str(shell)
963                    .map(|s| s.to_string().into())
964            }
965            _ => self.try_get_cow_str_without_dynamic_support(),
966        }
967    }
968
969    fn try_get_cow_str_without_dynamic_support(&self) -> Option<Cow<'_, str>> {
970        match self {
971            Self::Unset(_) => None,
972            Self::String(s) => Some(Cow::Borrowed(s.as_str())),
973            Self::AssociativeArray(values) => values.get("0").map(|s| Cow::Borrowed(s.as_str())),
974            Self::IndexedArray(values) => values.get(&0).map(|s| Cow::Borrowed(s.as_str())),
975            Self::Dynamic { .. } => None,
976        }
977    }
978
979    /// Formats this value as a program string usable in an assignment.
980    ///
981    /// # Arguments
982    ///
983    /// * `index` - The index at which to retrieve the value, if indexing is to be performed.
984    pub fn to_assignable_str(&self, index: Option<&str>, shell: &Shell) -> String {
985        match self {
986            Self::Unset(_) => String::new(),
987            Self::String(s) => escape::force_quote(s.as_str(), escape::QuoteMode::SingleQuote),
988            Self::AssociativeArray(_) | Self::IndexedArray(_) => {
989                if let Some(index) = index {
990                    if let Ok(Some(value)) = self.get_at(index, shell) {
991                        escape::force_quote(value.as_ref(), escape::QuoteMode::SingleQuote)
992                    } else {
993                        String::new()
994                    }
995                } else {
996                    self.format(FormatStyle::DeclarePrint, shell)
997                        .unwrap()
998                        .into_owned()
999                }
1000            }
1001            Self::Dynamic { getter, .. } => getter(shell).to_assignable_str(index, shell),
1002        }
1003    }
1004}
1005
1006impl From<&str> for ShellValue {
1007    fn from(value: &str) -> Self {
1008        Self::String(value.to_owned())
1009    }
1010}
1011
1012impl From<&String> for ShellValue {
1013    fn from(value: &String) -> Self {
1014        Self::String(value.clone())
1015    }
1016}
1017
1018impl From<String> for ShellValue {
1019    fn from(value: String) -> Self {
1020        Self::String(value)
1021    }
1022}
1023
1024impl From<Vec<String>> for ShellValue {
1025    fn from(values: Vec<String>) -> Self {
1026        Self::indexed_array_from_strings(values)
1027    }
1028}
1029
1030impl From<Vec<&str>> for ShellValue {
1031    fn from(values: Vec<&str>) -> Self {
1032        Self::indexed_array_from_strs(values.as_slice())
1033    }
1034}