brush_core/
variables.rs

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