1use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::ffi::OsString;
6use std::fmt::{Display, Write};
7
8use crate::shell::{Shell, ShellState};
9use crate::{error, escape, extensions};
10
11#[derive(Clone, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct ShellVariable {
15 value: ShellValue,
17 exported: bool,
19 readonly: bool,
21 enumerable: bool,
23 transform_on_update: ShellVariableUpdateTransform,
25 trace: bool,
27 treat_as_integer: bool,
29 treat_as_nameref: bool,
31}
32
33#[derive(Clone, Copy, Debug)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36pub enum ShellVariableUpdateTransform {
37 None,
39 Lowercase,
41 Uppercase,
43 Capitalize,
45}
46
47impl Default for ShellVariable {
48 fn default() -> Self {
49 Self {
50 value: ShellValue::String(String::new()),
51 exported: false,
52 readonly: false,
53 enumerable: true,
54 transform_on_update: ShellVariableUpdateTransform::None,
55 trace: false,
56 treat_as_integer: false,
57 treat_as_nameref: false,
58 }
59 }
60}
61
62impl ShellVariable {
63 pub fn new<I: Into<ShellValue>>(value: I) -> Self {
69 Self {
70 value: value.into(),
71 ..Self::default()
72 }
73 }
74
75 pub const fn value(&self) -> &ShellValue {
77 &self.value
78 }
79
80 pub const fn is_exported(&self) -> bool {
82 self.exported
83 }
84
85 pub const fn export(&mut self) -> &mut Self {
87 self.exported = true;
88 self
89 }
90
91 pub const fn unexport(&mut self) -> &mut Self {
93 self.exported = false;
94 self
95 }
96
97 pub const fn is_readonly(&self) -> bool {
99 self.readonly
100 }
101
102 pub const fn set_readonly(&mut self) -> &mut Self {
104 self.readonly = true;
105 self
106 }
107
108 pub fn unset_readonly(&mut self) -> Result<&mut Self, error::Error> {
110 if self.readonly {
111 return Err(error::ErrorKind::ReadonlyVariable.into());
112 }
113
114 self.readonly = false;
115 Ok(self)
116 }
117
118 pub const fn is_trace_enabled(&self) -> bool {
120 self.trace
121 }
122
123 pub const fn enable_trace(&mut self) -> &mut Self {
125 self.trace = true;
126 self
127 }
128
129 pub const fn disable_trace(&mut self) -> &mut Self {
131 self.trace = false;
132 self
133 }
134
135 pub const fn is_enumerable(&self) -> bool {
137 self.enumerable
138 }
139
140 pub const fn hide_from_enumeration(&mut self) -> &mut Self {
142 self.enumerable = false;
143 self
144 }
145
146 pub const fn get_update_transform(&self) -> ShellVariableUpdateTransform {
148 self.transform_on_update
149 }
150
151 pub const fn set_update_transform(&mut self, transform: ShellVariableUpdateTransform) {
153 self.transform_on_update = transform;
154 }
155
156 pub const fn is_treated_as_integer(&self) -> bool {
158 self.treat_as_integer
159 }
160
161 pub const fn treat_as_integer(&mut self) -> &mut Self {
163 self.treat_as_integer = true;
164 self
165 }
166
167 pub const fn unset_treat_as_integer(&mut self) -> &mut Self {
169 self.treat_as_integer = false;
170 self
171 }
172
173 pub const fn is_treated_as_nameref(&self) -> bool {
175 self.treat_as_nameref
176 }
177
178 pub const fn treat_as_nameref(&mut self) -> &mut Self {
180 self.treat_as_nameref = true;
181 self
182 }
183
184 pub const fn unset_treat_as_nameref(&mut self) -> &mut Self {
186 self.treat_as_nameref = false;
187 self
188 }
189
190 pub fn convert_to_indexed_array(&mut self) -> Result<(), error::Error> {
192 match self.value() {
193 ShellValue::IndexedArray(_) => Ok(()),
194 ShellValue::AssociativeArray(_) => {
195 Err(error::ErrorKind::ConvertingAssociativeArrayToIndexedArray.into())
196 }
197 _ => {
198 let mut new_values = BTreeMap::new();
199 new_values.insert(
200 0,
201 self.value.to_cow_str_without_dynamic_support().to_string(),
202 );
203 self.value = ShellValue::IndexedArray(new_values);
204 Ok(())
205 }
206 }
207 }
208
209 pub fn convert_to_associative_array(&mut self) -> Result<(), error::Error> {
211 match self.value() {
212 ShellValue::AssociativeArray(_) => Ok(()),
213 ShellValue::IndexedArray(_) => {
214 Err(error::ErrorKind::ConvertingIndexedArrayToAssociativeArray.into())
215 }
216 _ => {
217 let mut new_values: BTreeMap<String, String> = BTreeMap::new();
218 new_values.insert(
219 String::from("0"),
220 self.value.to_cow_str_without_dynamic_support().to_string(),
221 );
222 self.value = ShellValue::AssociativeArray(new_values);
223 Ok(())
224 }
225 }
226 }
227
228 #[expect(clippy::too_many_lines)]
235 pub fn assign(&mut self, value: ShellValueLiteral, append: bool) -> Result<(), error::Error> {
236 if self.is_readonly() {
237 return Err(error::ErrorKind::ReadonlyVariable.into());
238 }
239
240 let value = self.convert_value_literal_for_assignment(value);
241
242 if append {
243 match (&self.value, &value) {
244 (ShellValue::Unset(_), ShellValueLiteral::Array(_))
247 | (
248 ShellValue::Unset(
249 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray,
250 ),
251 _,
252 ) => {
253 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
254 }
255 (ShellValue::Unset(_), ShellValueLiteral::Scalar(_)) => {
259 self.assign(ShellValueLiteral::Scalar(String::new()), false)?;
260 }
261 (ShellValue::String(_), ShellValueLiteral::Array(_)) => {
264 self.convert_to_indexed_array()?;
265 }
266 _ => (),
267 }
268
269 let treat_as_int = self.is_treated_as_integer();
270 let update_transform = self.get_update_transform();
271
272 match &mut self.value {
273 ShellValue::String(base) => match value {
274 ShellValueLiteral::Scalar(suffix) => {
275 if treat_as_int {
276 let int_value = base.parse::<i64>().unwrap_or(0)
277 + suffix.parse::<i64>().unwrap_or(0);
278 base.clear();
279 base.push_str(int_value.to_string().as_str());
280 } else {
281 base.push_str(suffix.as_str());
282 Self::apply_value_transforms(base, treat_as_int, update_transform);
283 }
284 Ok(())
285 }
286 ShellValueLiteral::Array(_) => {
287 Ok(())
289 }
290 },
291 ShellValue::IndexedArray(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_indexed_array_from_literals(existing_values, new_values);
297 Ok(())
298 }
299 },
300 ShellValue::AssociativeArray(existing_values) => match value {
301 ShellValueLiteral::Scalar(new_value) => {
302 self.assign_at_index(String::from("0"), new_value, append)
303 }
304 ShellValueLiteral::Array(new_values) => {
305 ShellValue::update_associative_array_from_literals(
306 existing_values,
307 new_values,
308 )
309 }
310 },
311 ShellValue::Unset(_) => unreachable!("covered in conversion above"),
312 ShellValue::Dynamic { .. } => Ok(()),
314 }
315 } else {
316 match (&self.value, value) {
317 (
320 ShellValue::IndexedArray(_)
321 | ShellValue::AssociativeArray(_)
322 | ShellValue::Unset(
323 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray,
324 ),
325 ShellValueLiteral::Scalar(s),
326 ) => self.assign_at_index(String::from("0"), s, false),
327
328 (
332 ShellValue::IndexedArray(_)
333 | ShellValue::Unset(
334 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::Untyped,
335 )
336 | ShellValue::String(_)
337 | ShellValue::Dynamic { .. },
338 ShellValueLiteral::Array(literal_values),
339 ) => {
340 self.value = ShellValue::indexed_array_from_literals(literal_values);
341 Ok(())
342 }
343
344 (
347 ShellValue::AssociativeArray(_)
348 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray),
349 ShellValueLiteral::Array(literal_values),
350 ) => {
351 self.value = ShellValue::associative_array_from_literals(literal_values)?;
352 Ok(())
353 }
354
355 (ShellValue::Dynamic { .. }, _) => Ok(()),
358
359 (ShellValue::String(_) | ShellValue::Unset(_), ShellValueLiteral::Scalar(s)) => {
361 self.value = ShellValue::String(s);
362 Ok(())
363 }
364 }
365 }
366 }
367
368 pub fn assign_at_index(
378 &mut self,
379 array_index: String,
380 value: String,
381 append: bool,
382 ) -> Result<(), error::Error> {
383 match &self.value {
384 ShellValue::Unset(_) => {
385 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
386 }
387 ShellValue::String(_) => {
388 self.convert_to_indexed_array()?;
389 }
390 _ => (),
391 }
392
393 let treat_as_int = self.is_treated_as_integer();
394 let value = self.convert_value_str_for_assignment(value);
395
396 match &mut self.value {
397 ShellValue::IndexedArray(arr) => {
398 let key = get_key_for_indexed_array(arr, array_index.as_str())?;
399
400 if append {
401 let existing_value = arr.get(&key).map_or_else(|| "", |v| v.as_str());
402
403 let mut new_value;
404 if treat_as_int {
405 new_value = (existing_value.parse::<i64>().unwrap_or(0)
406 + value.parse::<i64>().unwrap_or(0))
407 .to_string();
408 } else {
409 new_value = existing_value.to_owned();
410 new_value.push_str(value.as_str());
411 }
412
413 arr.insert(key, new_value);
414 } else {
415 arr.insert(key, value);
416 }
417
418 Ok(())
419 }
420 ShellValue::AssociativeArray(arr) => {
421 if append {
422 let existing_value = arr
423 .get(array_index.as_str())
424 .map_or_else(|| "", |v| v.as_str());
425
426 let mut new_value;
427 if treat_as_int {
428 new_value = (existing_value.parse::<i64>().unwrap_or(0)
429 + value.parse::<i64>().unwrap_or(0))
430 .to_string();
431 } else {
432 new_value = existing_value.to_owned();
433 new_value.push_str(value.as_str());
434 }
435
436 arr.insert(array_index, new_value.clone());
437 } else {
438 arr.insert(array_index, value);
439 }
440 Ok(())
441 }
442 _ => {
443 tracing::error!("assigning to index {array_index} of {:?}", self.value);
444 error::unimp("assigning to index of non-array variable")
445 }
446 }
447 }
448
449 fn convert_value_literal_for_assignment(&self, value: ShellValueLiteral) -> ShellValueLiteral {
450 match value {
451 ShellValueLiteral::Scalar(s) => {
452 ShellValueLiteral::Scalar(self.convert_value_str_for_assignment(s))
453 }
454 ShellValueLiteral::Array(literals) => ShellValueLiteral::Array(ArrayLiteral(
455 literals
456 .0
457 .into_iter()
458 .map(|(k, v)| (k, self.convert_value_str_for_assignment(v)))
459 .collect(),
460 )),
461 }
462 }
463
464 fn convert_value_str_for_assignment(&self, mut s: String) -> String {
465 Self::apply_value_transforms(
466 &mut s,
467 self.is_treated_as_integer(),
468 self.get_update_transform(),
469 );
470
471 s
472 }
473
474 fn apply_value_transforms(
475 s: &mut String,
476 treat_as_int: bool,
477 update_transform: ShellVariableUpdateTransform,
478 ) {
479 if treat_as_int {
480 *s = (*s).parse::<i64>().unwrap_or(0).to_string();
481 } else {
482 match update_transform {
483 ShellVariableUpdateTransform::None => (),
484 ShellVariableUpdateTransform::Lowercase => *s = (*s).to_lowercase(),
485 ShellVariableUpdateTransform::Uppercase => *s = (*s).to_uppercase(),
486 ShellVariableUpdateTransform::Capitalize => {
487 *s = s.to_lowercase();
489 if let Some(c) = s.chars().next() {
490 s.replace_range(0..1, &c.to_uppercase().to_string());
491 }
492 }
493 }
494 }
495 }
496
497 pub fn unset_index(&mut self, index: &str) -> Result<bool, error::Error> {
504 match &mut self.value {
505 ShellValue::Unset(ty) => match ty {
506 ShellValueUnsetType::Untyped => Err(error::ErrorKind::NotArray.into()),
507 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray => {
508 Ok(false)
509 }
510 },
511 ShellValue::String(_) => Err(error::ErrorKind::NotArray.into()),
512 ShellValue::AssociativeArray(values) => Ok(values.remove(index).is_some()),
513 ShellValue::IndexedArray(values) => {
514 let key = get_key_for_indexed_array(values, index)?;
515 Ok(values.remove(&key).is_some())
516 }
517 ShellValue::Dynamic { .. } => Ok(false),
518 }
519 }
520
521 pub fn resolve_value(&self, shell: &Shell<impl extensions::ShellExtensions>) -> ShellValue {
527 match &self.value {
529 ShellValue::Dynamic { getter, .. } => getter(shell),
530 _ => self.value.clone(),
531 }
532 }
533
534 pub fn attribute_flags(&self, shell: &Shell<impl extensions::ShellExtensions>) -> String {
536 let value = self.resolve_value(shell);
537
538 let mut result = String::new();
539
540 if matches!(
541 value,
542 ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
543 ) {
544 result.push('a');
545 }
546 if matches!(
547 value,
548 ShellValue::AssociativeArray(_)
549 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray)
550 ) {
551 result.push('A');
552 }
553 if matches!(
554 self.get_update_transform(),
555 ShellVariableUpdateTransform::Capitalize
556 ) {
557 result.push('c');
558 }
559 if self.is_treated_as_integer() {
560 result.push('i');
561 }
562 if self.is_treated_as_nameref() {
563 result.push('n');
564 }
565 if self.is_readonly() {
566 result.push('r');
567 }
568 if matches!(
569 self.get_update_transform(),
570 ShellVariableUpdateTransform::Lowercase
571 ) {
572 result.push('l');
573 }
574 if self.is_trace_enabled() {
575 result.push('t');
576 }
577 if matches!(
578 self.get_update_transform(),
579 ShellVariableUpdateTransform::Uppercase
580 ) {
581 result.push('u');
582 }
583 if self.is_exported() {
584 result.push('x');
585 }
586
587 result
588 }
589}
590
591type DynamicValueGetter = fn(&dyn ShellState) -> ShellValue;
592type DynamicValueSetter = fn(&dyn ShellState) -> ();
593
594#[derive(Clone, Debug)]
596#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
597pub enum ShellValue {
598 Unset(ShellValueUnsetType),
600 String(String),
602 AssociativeArray(BTreeMap<String, String>),
604 IndexedArray(BTreeMap<u64, String>),
606 Dynamic {
608 #[cfg_attr(
611 feature = "serde",
612 serde(skip, default = "default_dynamic_value_getter")
613 )]
614 getter: DynamicValueGetter,
615 #[cfg_attr(
618 feature = "serde",
619 serde(skip, default = "default_dynamic_value_setter")
620 )]
621 setter: DynamicValueSetter,
622 },
623}
624
625#[cfg(feature = "serde")]
626fn default_dynamic_value_getter() -> DynamicValueGetter {
627 |_shell: &dyn ShellState| ShellValue::String(String::new())
628}
629
630#[cfg(feature = "serde")]
631fn default_dynamic_value_setter() -> DynamicValueSetter {
632 |_shell: &dyn ShellState| {}
633}
634
635#[derive(Clone, Debug)]
637#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
638pub enum ShellValueUnsetType {
639 Untyped,
641 AssociativeArray,
643 IndexedArray,
645}
646
647#[derive(Clone, Debug)]
649pub enum ShellValueLiteral {
650 Scalar(String),
652 Array(ArrayLiteral),
654}
655
656impl ShellValueLiteral {
657 pub(crate) fn fmt_for_tracing(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658 match self {
659 Self::Scalar(s) => Self::fmt_scalar_for_tracing(s.as_str(), f),
660 Self::Array(elements) => {
661 write!(f, "(")?;
662 for (i, (key, value)) in elements.0.iter().enumerate() {
663 if i > 0 {
664 write!(f, " ")?;
665 }
666 if let Some(key) = key {
667 write!(f, "[")?;
668 Self::fmt_scalar_for_tracing(key.as_str(), f)?;
669 write!(f, "]=")?;
670 }
671 Self::fmt_scalar_for_tracing(value.as_str(), f)?;
672 }
673 write!(f, ")")
674 }
675 }
676 }
677
678 fn fmt_scalar_for_tracing(s: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
679 let processed = escape::quote_if_needed(s, escape::QuoteMode::SingleQuote);
680 write!(f, "{processed}")
681 }
682}
683
684impl Display for ShellValueLiteral {
685 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686 self.fmt_for_tracing(f)
687 }
688}
689
690impl From<&str> for ShellValueLiteral {
691 fn from(value: &str) -> Self {
692 Self::Scalar(value.to_owned())
693 }
694}
695
696impl From<String> for ShellValueLiteral {
697 fn from(value: String) -> Self {
698 Self::Scalar(value)
699 }
700}
701
702impl From<Vec<&str>> for ShellValueLiteral {
703 fn from(value: Vec<&str>) -> Self {
704 Self::Array(ArrayLiteral(
705 value.into_iter().map(|s| (None, s.to_owned())).collect(),
706 ))
707 }
708}
709
710#[derive(Clone, Debug)]
712pub struct ArrayLiteral(pub Vec<(Option<String>, String)>);
713
714#[derive(Copy, Clone, Debug)]
716pub enum FormatStyle {
717 Basic,
719 DeclarePrint,
721}
722
723impl ShellValue {
724 pub const fn is_array(&self) -> bool {
726 matches!(
727 self,
728 Self::IndexedArray(_)
729 | Self::AssociativeArray(_)
730 | Self::Unset(
731 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray
732 )
733 )
734 }
735
736 pub const fn is_set(&self) -> bool {
738 !matches!(self, Self::Unset(_))
739 }
740
741 pub fn indexed_array_from_strings<S>(values: S) -> Self
747 where
748 S: IntoIterator<Item = String>,
749 {
750 let mut owned_values = BTreeMap::new();
751 for (i, value) in values.into_iter().enumerate() {
752 owned_values.insert(i as u64, value);
753 }
754
755 Self::IndexedArray(owned_values)
756 }
757
758 pub fn indexed_array_from_strs(values: &[&str]) -> Self {
764 let mut owned_values = BTreeMap::new();
765 for (i, value) in values.iter().enumerate() {
766 owned_values.insert(i as u64, (*value).to_string());
767 }
768
769 Self::IndexedArray(owned_values)
770 }
771
772 pub fn indexed_array_from_literals(literals: ArrayLiteral) -> Self {
778 let mut values = BTreeMap::new();
779 Self::update_indexed_array_from_literals(&mut values, literals);
780
781 Self::IndexedArray(values)
782 }
783
784 fn update_indexed_array_from_literals(
785 existing_values: &mut BTreeMap<u64, String>,
786 literal_values: ArrayLiteral,
787 ) {
788 let mut new_key = if let Some((largest_index, _)) = existing_values.last_key_value() {
789 largest_index + 1
790 } else {
791 0
792 };
793
794 for (key, value) in literal_values.0 {
795 if let Some(key) = key {
796 new_key = key.parse().unwrap_or(0);
797 }
798
799 existing_values.insert(new_key, value);
800 new_key += 1;
801 }
802 }
803
804 pub fn associative_array_from_literals(literals: ArrayLiteral) -> Result<Self, error::Error> {
810 let mut values = BTreeMap::new();
811 Self::update_associative_array_from_literals(&mut values, literals)?;
812
813 Ok(Self::AssociativeArray(values))
814 }
815
816 fn update_associative_array_from_literals(
817 existing_values: &mut BTreeMap<String, String>,
818 literal_values: ArrayLiteral,
819 ) -> Result<(), error::Error> {
820 let mut current_key = None;
821 for (key, value) in literal_values.0 {
822 if let Some(current_key) = current_key.take() {
823 if key.is_some() {
824 return error::unimp("misaligned keys/values in associative array literal");
825 } else {
826 existing_values.insert(current_key, value);
827 }
828 } else if let Some(key) = key {
829 existing_values.insert(key, value);
830 } else {
831 current_key = Some(value);
832 }
833 }
834
835 if let Some(current_key) = current_key {
836 existing_values.insert(current_key, String::new());
837 }
838
839 Ok(())
840 }
841
842 pub fn format(
848 &self,
849 style: FormatStyle,
850 shell: &Shell<impl extensions::ShellExtensions>,
851 ) -> Result<Cow<'_, str>, error::Error> {
852 match self {
853 Self::Unset(_) => Ok("".into()),
854 Self::String(s) => match style {
855 FormatStyle::Basic => Ok(escape::quote_if_needed(
856 s.as_str(),
857 escape::QuoteMode::SingleQuote,
858 )),
859 FormatStyle::DeclarePrint => {
860 Ok(escape::force_quote(s.as_str(), escape::QuoteMode::DoubleQuote).into())
861 }
862 },
863 Self::AssociativeArray(values) => {
864 let mut result = String::new();
865 result.push('(');
866
867 for (key, value) in values {
868 let formatted_key =
869 escape::quote_if_needed(key.as_str(), escape::QuoteMode::DoubleQuote);
870 let formatted_value =
871 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
872
873 write!(result, "[{formatted_key}]={formatted_value} ")?;
877 }
878
879 result.push(')');
880 Ok(result.into())
881 }
882 Self::IndexedArray(values) => {
883 let mut result = String::new();
884 result.push('(');
885
886 for (i, (key, value)) in values.iter().enumerate() {
887 if i > 0 {
888 result.push(' ');
889 }
890
891 let formatted_value =
892 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
893 write!(result, "[{key}]={formatted_value}")?;
894 }
895
896 result.push(')');
897 Ok(result.into())
898 }
899 Self::Dynamic { getter, .. } => {
900 let dynamic_value = getter(shell);
901 let result = dynamic_value.format(style, shell)?.to_string();
902 Ok(result.into())
903 }
904 }
905 }
906
907 pub fn get_at(
913 &self,
914 index: &str,
915 shell: &Shell<impl extensions::ShellExtensions>,
916 ) -> Result<Option<Cow<'_, str>>, error::Error> {
917 match self {
918 Self::Unset(_) => Ok(None),
919 Self::String(s) => {
920 if index.parse::<u64>().unwrap_or(0) == 0 {
921 Ok(Some(Cow::Borrowed(s)))
922 } else {
923 Ok(None)
924 }
925 }
926 Self::AssociativeArray(values) => {
927 Ok(values.get(index).map(|s| Cow::Borrowed(s.as_str())))
928 }
929 Self::IndexedArray(values) => {
930 let key = get_key_for_indexed_array(values, index)?;
931 Ok(values.get(&key).map(|s| Cow::Borrowed(s.as_str())))
932 }
933 Self::Dynamic { getter, .. } => {
934 let dynamic_value = getter(shell);
935 let result = dynamic_value.get_at(index, shell)?;
936 Ok(result.map(|s| s.to_string().into()))
937 }
938 }
939 }
940
941 pub fn element_keys(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
943 match self {
944 Self::Unset(_) => vec![],
945 Self::String(_) => vec!["0".to_owned()],
946 Self::AssociativeArray(array) => array.keys().map(|k| k.to_owned()).collect(),
947 Self::IndexedArray(array) => array.keys().map(|k| k.to_string()).collect(),
948 Self::Dynamic { getter, .. } => getter(shell).element_keys(shell),
949 }
950 }
951
952 pub fn element_values(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
954 match self {
955 Self::Unset(_) => vec![],
956 Self::String(s) => vec![s.to_owned()],
957 Self::AssociativeArray(array) => array.values().map(|v| v.to_owned()).collect(),
958 Self::IndexedArray(array) => array.values().map(|v| v.to_owned()).collect(),
959 Self::Dynamic { getter, .. } => getter(shell).element_values(shell),
960 }
961 }
962
963 pub fn to_cow_str(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Cow<'_, str> {
965 self.try_get_cow_str(shell).unwrap_or(Cow::Borrowed(""))
966 }
967
968 fn to_cow_str_without_dynamic_support(&self) -> Cow<'_, str> {
969 self.try_get_cow_str_without_dynamic_support()
970 .unwrap_or(Cow::Borrowed(""))
971 }
972
973 pub fn try_get_cow_str(
976 &self,
977 shell: &Shell<impl extensions::ShellExtensions>,
978 ) -> Option<Cow<'_, str>> {
979 match self {
980 Self::Dynamic { getter, .. } => {
981 let dynamic_value = getter(shell);
982 dynamic_value
983 .try_get_cow_str(shell)
984 .map(|s| s.to_string().into())
985 }
986 _ => self.try_get_cow_str_without_dynamic_support(),
987 }
988 }
989
990 fn try_get_cow_str_without_dynamic_support(&self) -> Option<Cow<'_, str>> {
991 match self {
992 Self::Unset(_) => None,
993 Self::String(s) => Some(Cow::Borrowed(s.as_str())),
994 Self::AssociativeArray(values) => values.get("0").map(|s| Cow::Borrowed(s.as_str())),
995 Self::IndexedArray(values) => values.get(&0).map(|s| Cow::Borrowed(s.as_str())),
996 Self::Dynamic { .. } => None,
997 }
998 }
999
1000 pub fn to_assignable_str(
1006 &self,
1007 index: Option<&str>,
1008 shell: &Shell<impl extensions::ShellExtensions>,
1009 ) -> Result<String, error::Error> {
1010 match self {
1011 Self::Unset(_) => Ok(String::new()),
1012 Self::String(s) => Ok(escape::force_quote(
1013 s.as_str(),
1014 escape::QuoteMode::SingleQuote,
1015 )),
1016 Self::AssociativeArray(_) | Self::IndexedArray(_) => {
1017 if let Some(index) = index {
1018 if let Ok(Some(value)) = self.get_at(index, shell) {
1019 Ok(escape::force_quote(
1020 value.as_ref(),
1021 escape::QuoteMode::SingleQuote,
1022 ))
1023 } else {
1024 Ok(String::new())
1025 }
1026 } else {
1027 Ok(self.format(FormatStyle::DeclarePrint, shell)?.into_owned())
1028 }
1029 }
1030 Self::Dynamic { getter, .. } => getter(shell).to_assignable_str(index, shell),
1031 }
1032 }
1033}
1034
1035fn get_key_for_indexed_array(
1036 values: &BTreeMap<u64, String>,
1037 index_str: &str,
1038) -> Result<u64, error::Error> {
1039 let mut index_value = index_str.parse::<i64>().unwrap_or(0);
1040
1041 #[expect(clippy::cast_possible_wrap)]
1043 if index_value < 0 {
1044 index_value += values.len() as i64;
1045 if index_value < 0 {
1046 return Err(error::ErrorKind::ArrayIndexOutOfRange(index_str.to_owned()).into());
1047 }
1048 }
1049
1050 #[expect(clippy::cast_sign_loss)]
1053 Ok(index_value as u64)
1054}
1055
1056impl From<&str> for ShellValue {
1057 fn from(value: &str) -> Self {
1058 Self::String(value.to_owned())
1059 }
1060}
1061
1062impl From<&String> for ShellValue {
1063 fn from(value: &String) -> Self {
1064 Self::String(value.clone())
1065 }
1066}
1067
1068impl From<String> for ShellValue {
1069 fn from(value: String) -> Self {
1070 Self::String(value)
1071 }
1072}
1073
1074impl From<OsString> for ShellValue {
1075 fn from(value: OsString) -> Self {
1076 Self::String(value.to_string_lossy().into_owned())
1077 }
1078}
1079
1080impl From<Vec<String>> for ShellValue {
1081 fn from(values: Vec<String>) -> Self {
1082 Self::indexed_array_from_strings(values)
1083 }
1084}
1085
1086impl From<Vec<&str>> for ShellValue {
1087 fn from(values: Vec<&str>) -> Self {
1088 Self::indexed_array_from_strs(values.as_slice())
1089 }
1090}