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,
24 transform_on_update: ShellVariableUpdateTransform,
26 trace: bool,
28 treat_as_integer: bool,
30 treat_as_nameref: bool,
32}
33
34#[derive(Clone, Copy, Debug)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum ShellVariableUpdateTransform {
38 None,
40 Lowercase,
42 Uppercase,
44 Capitalize,
46}
47
48impl Default for ShellVariable {
49 fn default() -> Self {
50 Self {
51 value: ShellValue::String(String::new()),
52 exported: false,
53 readonly: false,
54 enumerable: true,
55 transform_on_update: ShellVariableUpdateTransform::None,
56 trace: false,
57 treat_as_integer: false,
58 treat_as_nameref: false,
59 }
60 }
61}
62
63impl ShellVariable {
64 pub fn new<I: Into<ShellValue>>(value: I) -> Self {
70 Self {
71 value: value.into(),
72 ..Self::default()
73 }
74 }
75
76 pub const fn value(&self) -> &ShellValue {
78 &self.value
79 }
80
81 pub const fn is_exported(&self) -> bool {
83 self.exported
84 }
85
86 pub const fn export(&mut self) -> &mut Self {
88 self.exported = true;
89 self
90 }
91
92 pub const fn unexport(&mut self) -> &mut Self {
94 self.exported = false;
95 self
96 }
97
98 pub const fn is_readonly(&self) -> bool {
100 self.readonly
101 }
102
103 pub const fn set_readonly(&mut self) -> &mut Self {
105 self.readonly = true;
106 self
107 }
108
109 pub fn unset_readonly(&mut self) -> Result<&mut Self, error::Error> {
116 if self.readonly {
117 return Err(error::ErrorKind::ReadonlyVariable.into());
118 }
119
120 self.readonly = false;
121 Ok(self)
122 }
123
124 pub const fn is_trace_enabled(&self) -> bool {
126 self.trace
127 }
128
129 pub const fn enable_trace(&mut self) -> &mut Self {
131 self.trace = true;
132 self
133 }
134
135 pub const fn disable_trace(&mut self) -> &mut Self {
137 self.trace = false;
138 self
139 }
140
141 pub const fn is_enumerable(&self) -> bool {
144 self.enumerable
145 }
146
147 pub const fn hide_from_enumeration(&mut self) -> &mut Self {
149 self.enumerable = false;
150 self
151 }
152
153 pub const fn get_update_transform(&self) -> ShellVariableUpdateTransform {
155 self.transform_on_update
156 }
157
158 pub const fn set_update_transform(&mut self, transform: ShellVariableUpdateTransform) {
160 self.transform_on_update = transform;
161 }
162
163 pub const fn is_treated_as_integer(&self) -> bool {
165 self.treat_as_integer
166 }
167
168 pub const fn treat_as_integer(&mut self) -> &mut Self {
170 self.treat_as_integer = true;
171 self
172 }
173
174 pub const fn unset_treat_as_integer(&mut self) -> &mut Self {
176 self.treat_as_integer = false;
177 self
178 }
179
180 pub const fn is_treated_as_nameref(&self) -> bool {
183 self.treat_as_nameref
184 }
185
186 pub const fn treat_as_nameref(&mut self) -> &mut Self {
188 self.treat_as_nameref = true;
189 self
190 }
191
192 pub const fn unset_treat_as_nameref(&mut self) -> &mut Self {
194 self.treat_as_nameref = false;
195 self
196 }
197
198 pub fn convert_to_indexed_array(&mut self) -> Result<(), error::Error> {
205 match self.value() {
206 ShellValue::IndexedArray(_) => Ok(()),
207 ShellValue::AssociativeArray(_) => {
208 Err(error::ErrorKind::ConvertingAssociativeArrayToIndexedArray.into())
209 }
210 _ => {
211 let mut new_values = BTreeMap::new();
212 new_values.insert(
213 0,
214 self.value.to_cow_str_without_dynamic_support().to_string(),
215 );
216 self.value = ShellValue::IndexedArray(new_values);
217 Ok(())
218 }
219 }
220 }
221
222 pub fn convert_to_associative_array(&mut self) -> Result<(), error::Error> {
229 match self.value() {
230 ShellValue::AssociativeArray(_) => Ok(()),
231 ShellValue::IndexedArray(_) => {
232 Err(error::ErrorKind::ConvertingIndexedArrayToAssociativeArray.into())
233 }
234 _ => {
235 let mut new_values: BTreeMap<String, String> = BTreeMap::new();
236 new_values.insert(
237 String::from("0"),
238 self.value.to_cow_str_without_dynamic_support().to_string(),
239 );
240 self.value = ShellValue::AssociativeArray(new_values);
241 Ok(())
242 }
243 }
244 }
245
246 #[expect(clippy::too_many_lines)]
263 pub fn assign(&mut self, value: ShellValueLiteral, append: bool) -> Result<(), error::Error> {
264 if self.is_readonly() {
265 return Err(error::ErrorKind::ReadonlyVariable.into());
266 }
267
268 let value = self.convert_value_literal_for_assignment(value);
269
270 if append {
271 match (&self.value, &value) {
272 (ShellValue::Unset(_), ShellValueLiteral::Array(_))
275 | (
276 ShellValue::Unset(
277 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray,
278 ),
279 _,
280 ) => {
281 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
282 }
283 (ShellValue::Unset(_), ShellValueLiteral::Scalar(_)) => {
287 self.assign(ShellValueLiteral::Scalar(String::new()), false)?;
288 }
289 (ShellValue::String(_), ShellValueLiteral::Array(_)) => {
292 self.convert_to_indexed_array()?;
293 }
294 _ => (),
295 }
296
297 let treat_as_int = self.is_treated_as_integer();
298 let update_transform = self.get_update_transform();
299
300 match &mut self.value {
301 ShellValue::String(base) => match value {
302 ShellValueLiteral::Scalar(suffix) => {
303 if treat_as_int {
304 let int_value = base.parse::<i64>().unwrap_or(0)
305 + suffix.parse::<i64>().unwrap_or(0);
306 base.clear();
307 base.push_str(int_value.to_string().as_str());
308 } else {
309 base.push_str(suffix.as_str());
310 Self::apply_value_transforms(base, treat_as_int, update_transform);
311 }
312 Ok(())
313 }
314 ShellValueLiteral::Array(_) => {
315 Ok(())
317 }
318 },
319 ShellValue::IndexedArray(existing_values) => match value {
320 ShellValueLiteral::Scalar(new_value) => {
321 self.assign_at_index(String::from("0"), new_value, append)
322 }
323 ShellValueLiteral::Array(new_values) => {
324 ShellValue::update_indexed_array_from_literals(existing_values, new_values);
325 Ok(())
326 }
327 },
328 ShellValue::AssociativeArray(existing_values) => match value {
329 ShellValueLiteral::Scalar(new_value) => {
330 self.assign_at_index(String::from("0"), new_value, append)
331 }
332 ShellValueLiteral::Array(new_values) => {
333 ShellValue::update_associative_array_from_literals(
334 existing_values,
335 new_values,
336 )
337 }
338 },
339 ShellValue::Unset(_) => unreachable!("covered in conversion above"),
340 ShellValue::Dynamic { .. } => Ok(()),
342 }
343 } else {
344 match (&self.value, value) {
345 (
348 ShellValue::IndexedArray(_)
349 | ShellValue::AssociativeArray(_)
350 | ShellValue::Unset(
351 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray,
352 ),
353 ShellValueLiteral::Scalar(s),
354 ) => self.assign_at_index(String::from("0"), s, false),
355
356 (
360 ShellValue::IndexedArray(_)
361 | ShellValue::Unset(
362 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::Untyped,
363 )
364 | ShellValue::String(_)
365 | ShellValue::Dynamic { .. },
366 ShellValueLiteral::Array(literal_values),
367 ) => {
368 self.value = ShellValue::indexed_array_from_literals(literal_values);
369 Ok(())
370 }
371
372 (
375 ShellValue::AssociativeArray(_)
376 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray),
377 ShellValueLiteral::Array(literal_values),
378 ) => {
379 self.value = ShellValue::associative_array_from_literals(literal_values)?;
380 Ok(())
381 }
382
383 (ShellValue::Dynamic { .. }, _) => Ok(()),
386
387 (ShellValue::String(_) | ShellValue::Unset(_), ShellValueLiteral::Scalar(s)) => {
389 self.value = ShellValue::String(s);
390 Ok(())
391 }
392 }
393 }
394 }
395
396 pub fn assign_at_index(
415 &mut self,
416 array_index: String,
417 value: String,
418 append: bool,
419 ) -> Result<(), error::Error> {
420 match &self.value {
421 ShellValue::Unset(_) => {
422 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
423 }
424 ShellValue::String(_) => {
425 self.convert_to_indexed_array()?;
426 }
427 _ => (),
428 }
429
430 let treat_as_int = self.is_treated_as_integer();
431 let value = self.convert_value_str_for_assignment(value);
432
433 match &mut self.value {
434 ShellValue::IndexedArray(arr) => {
435 let key = get_key_for_indexed_array(arr, array_index.as_str())?;
436
437 if append {
438 let existing_value = arr.get(&key).map_or("", |v| v.as_str());
439
440 let mut new_value;
441 if treat_as_int {
442 new_value = (existing_value.parse::<i64>().unwrap_or(0)
443 + value.parse::<i64>().unwrap_or(0))
444 .to_string();
445 } else {
446 new_value = existing_value.to_owned();
447 new_value.push_str(value.as_str());
448 }
449
450 arr.insert(key, new_value);
451 } else {
452 arr.insert(key, value);
453 }
454
455 Ok(())
456 }
457 ShellValue::AssociativeArray(arr) => {
458 if append {
459 let existing_value = arr.get(array_index.as_str()).map_or("", |v| v.as_str());
460
461 let mut new_value;
462 if treat_as_int {
463 new_value = (existing_value.parse::<i64>().unwrap_or(0)
464 + value.parse::<i64>().unwrap_or(0))
465 .to_string();
466 } else {
467 new_value = existing_value.to_owned();
468 new_value.push_str(value.as_str());
469 }
470
471 arr.insert(array_index, new_value.clone());
472 } else {
473 arr.insert(array_index, value);
474 }
475 Ok(())
476 }
477 _ => {
478 tracing::error!("assigning to index {array_index} of {:?}", self.value);
479 error::unimp("assigning to index of non-array variable")
480 }
481 }
482 }
483
484 fn convert_value_literal_for_assignment(&self, value: ShellValueLiteral) -> ShellValueLiteral {
485 match value {
486 ShellValueLiteral::Scalar(s) => {
487 ShellValueLiteral::Scalar(self.convert_value_str_for_assignment(s))
488 }
489 ShellValueLiteral::Array(literals) => ShellValueLiteral::Array(ArrayLiteral(
490 literals
491 .0
492 .into_iter()
493 .map(|(k, v)| (k, self.convert_value_str_for_assignment(v)))
494 .collect(),
495 )),
496 }
497 }
498
499 fn convert_value_str_for_assignment(&self, mut s: String) -> String {
500 Self::apply_value_transforms(
501 &mut s,
502 self.is_treated_as_integer(),
503 self.get_update_transform(),
504 );
505
506 s
507 }
508
509 fn apply_value_transforms(
510 s: &mut String,
511 treat_as_int: bool,
512 update_transform: ShellVariableUpdateTransform,
513 ) {
514 if treat_as_int {
515 *s = (*s).parse::<i64>().unwrap_or(0).to_string();
516 } else {
517 match update_transform {
518 ShellVariableUpdateTransform::None => (),
519 ShellVariableUpdateTransform::Lowercase => *s = (*s).to_lowercase(),
520 ShellVariableUpdateTransform::Uppercase => *s = (*s).to_uppercase(),
521 ShellVariableUpdateTransform::Capitalize => {
522 *s = s.to_lowercase();
525 if let Some(c) = s.chars().next() {
526 s.replace_range(0..1, &c.to_uppercase().to_string());
527 }
528 }
529 }
530 }
531 }
532
533 pub fn unset_index(&mut self, index: &str) -> Result<bool, error::Error> {
548 match &mut self.value {
549 ShellValue::Unset(ty) => match ty {
550 ShellValueUnsetType::Untyped => Err(error::ErrorKind::NotArray.into()),
551 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray => {
552 Ok(false)
553 }
554 },
555 ShellValue::String(_) => Err(error::ErrorKind::NotArray.into()),
556 ShellValue::AssociativeArray(values) => Ok(values.remove(index).is_some()),
557 ShellValue::IndexedArray(values) => {
558 let key = get_key_for_indexed_array(values, index)?;
559 Ok(values.remove(&key).is_some())
560 }
561 ShellValue::Dynamic { .. } => Ok(false),
562 }
563 }
564
565 pub fn resolve_value(&self, shell: &Shell<impl extensions::ShellExtensions>) -> ShellValue {
572 match &self.value {
575 ShellValue::Dynamic { getter, .. } => getter(shell),
576 _ => self.value.clone(),
577 }
578 }
579
580 pub fn attribute_flags(&self, shell: &Shell<impl extensions::ShellExtensions>) -> String {
582 let value = self.resolve_value(shell);
583
584 let mut result = String::new();
585
586 if value.is_indexed_array() {
587 result.push('a');
588 }
589 if value.is_associative_array() {
590 result.push('A');
591 }
592 if matches!(
593 self.get_update_transform(),
594 ShellVariableUpdateTransform::Capitalize
595 ) {
596 result.push('c');
597 }
598 if self.is_treated_as_integer() {
599 result.push('i');
600 }
601 if self.is_treated_as_nameref() {
602 result.push('n');
603 }
604 if self.is_readonly() {
605 result.push('r');
606 }
607 if matches!(
608 self.get_update_transform(),
609 ShellVariableUpdateTransform::Lowercase
610 ) {
611 result.push('l');
612 }
613 if self.is_trace_enabled() {
614 result.push('t');
615 }
616 if matches!(
617 self.get_update_transform(),
618 ShellVariableUpdateTransform::Uppercase
619 ) {
620 result.push('u');
621 }
622 if self.is_exported() {
623 result.push('x');
624 }
625
626 result
627 }
628}
629
630type DynamicValueGetter = fn(&dyn ShellState) -> ShellValue;
631type DynamicValueSetter = fn(&dyn ShellState) -> ();
632
633#[derive(Clone, Debug)]
635#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
636pub enum ShellValue {
637 Unset(ShellValueUnsetType),
639 String(String),
641 AssociativeArray(BTreeMap<String, String>),
643 IndexedArray(BTreeMap<u64, String>),
645 Dynamic {
647 #[cfg_attr(
650 feature = "serde",
651 serde(skip, default = "default_dynamic_value_getter")
652 )]
653 getter: DynamicValueGetter,
654 #[cfg_attr(
657 feature = "serde",
658 serde(skip, default = "default_dynamic_value_setter")
659 )]
660 setter: DynamicValueSetter,
661 },
662}
663
664#[cfg(feature = "serde")]
665fn default_dynamic_value_getter() -> DynamicValueGetter {
666 |_shell: &dyn ShellState| ShellValue::String(String::new())
667}
668
669#[cfg(feature = "serde")]
670fn default_dynamic_value_setter() -> DynamicValueSetter {
671 |_shell: &dyn ShellState| {}
672}
673
674#[derive(Clone, Debug)]
676#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
677pub enum ShellValueUnsetType {
678 Untyped,
680 AssociativeArray,
682 IndexedArray,
684}
685
686#[derive(Clone, Debug)]
688pub enum ShellValueLiteral {
689 Scalar(String),
691 Array(ArrayLiteral),
693}
694
695impl ShellValueLiteral {
696 pub(crate) fn fmt_for_tracing(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
697 match self {
698 Self::Scalar(s) => Self::fmt_scalar_for_tracing(s.as_str(), f),
699 Self::Array(elements) => {
700 write!(f, "(")?;
701 for (i, (key, value)) in elements.0.iter().enumerate() {
702 if i > 0 {
703 write!(f, " ")?;
704 }
705 if let Some(key) = key {
706 write!(f, "[")?;
707 Self::fmt_scalar_for_tracing(key.as_str(), f)?;
708 write!(f, "]=")?;
709 }
710 Self::fmt_scalar_for_tracing(value.as_str(), f)?;
711 }
712 write!(f, ")")
713 }
714 }
715 }
716
717 fn fmt_scalar_for_tracing(s: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 let processed = escape::quote_if_needed(s, escape::QuoteMode::SingleQuote);
719 write!(f, "{processed}")
720 }
721}
722
723impl Display for ShellValueLiteral {
724 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
725 self.fmt_for_tracing(f)
726 }
727}
728
729impl From<&str> for ShellValueLiteral {
730 fn from(value: &str) -> Self {
731 Self::Scalar(value.to_owned())
732 }
733}
734
735impl From<String> for ShellValueLiteral {
736 fn from(value: String) -> Self {
737 Self::Scalar(value)
738 }
739}
740
741impl From<Vec<&str>> for ShellValueLiteral {
742 fn from(value: Vec<&str>) -> Self {
743 Self::Array(ArrayLiteral(
744 value.into_iter().map(|s| (None, s.to_owned())).collect(),
745 ))
746 }
747}
748
749#[derive(Clone, Debug)]
751pub struct ArrayLiteral(pub Vec<(Option<String>, String)>);
752
753#[derive(Copy, Clone, Debug)]
755pub enum FormatStyle {
756 Basic,
758 DeclarePrint,
760}
761
762impl ShellValue {
763 pub const fn is_indexed_array(&self) -> bool {
766 matches!(
767 self,
768 Self::IndexedArray(_) | Self::Unset(ShellValueUnsetType::IndexedArray)
769 )
770 }
771
772 pub const fn is_associative_array(&self) -> bool {
775 matches!(
776 self,
777 Self::AssociativeArray(_) | Self::Unset(ShellValueUnsetType::AssociativeArray)
778 )
779 }
780
781 pub const fn is_array(&self) -> bool {
783 self.is_indexed_array() || self.is_associative_array()
784 }
785
786 pub const fn is_set(&self) -> bool {
788 !matches!(self, Self::Unset(_))
789 }
790
791 pub fn indexed_array_from_strings<S>(values: S) -> Self
798 where
799 S: IntoIterator<Item = String>,
800 {
801 let mut owned_values = BTreeMap::new();
802 for (i, value) in values.into_iter().enumerate() {
803 owned_values.insert(i as u64, value);
804 }
805
806 Self::IndexedArray(owned_values)
807 }
808
809 pub fn indexed_array_from_strs(values: &[&str]) -> Self {
816 let mut owned_values = BTreeMap::new();
817 for (i, value) in values.iter().enumerate() {
818 owned_values.insert(i as u64, (*value).to_string());
819 }
820
821 Self::IndexedArray(owned_values)
822 }
823
824 pub fn indexed_array_from_literals(literals: ArrayLiteral) -> Self {
830 let mut values = BTreeMap::new();
831 Self::update_indexed_array_from_literals(&mut values, literals);
832
833 Self::IndexedArray(values)
834 }
835
836 fn update_indexed_array_from_literals(
837 existing_values: &mut BTreeMap<u64, String>,
838 literal_values: ArrayLiteral,
839 ) {
840 let mut new_key = if let Some((largest_index, _)) = existing_values.last_key_value() {
841 largest_index + 1
842 } else {
843 0
844 };
845
846 for (key, value) in literal_values.0 {
847 if let Some(key) = key {
848 new_key = key.parse().unwrap_or(0);
849 }
850
851 existing_values.insert(new_key, value);
852 new_key += 1;
853 }
854 }
855
856 pub fn associative_array_from_literals(literals: ArrayLiteral) -> Result<Self, error::Error> {
868 let mut values = BTreeMap::new();
869 Self::update_associative_array_from_literals(&mut values, literals)?;
870
871 Ok(Self::AssociativeArray(values))
872 }
873
874 fn update_associative_array_from_literals(
875 existing_values: &mut BTreeMap<String, String>,
876 literal_values: ArrayLiteral,
877 ) -> Result<(), error::Error> {
878 let mut current_key = None;
879 for (key, value) in literal_values.0 {
880 if let Some(current_key) = current_key.take() {
881 if key.is_some() {
882 return error::unimp("misaligned keys/values in associative array literal");
883 } else {
884 existing_values.insert(current_key, value);
885 }
886 } else if let Some(key) = key {
887 existing_values.insert(key, value);
888 } else {
889 current_key = Some(value);
890 }
891 }
892
893 if let Some(current_key) = current_key {
894 existing_values.insert(current_key, String::new());
895 }
896
897 Ok(())
898 }
899
900 pub fn format(
911 &self,
912 style: FormatStyle,
913 shell: &Shell<impl extensions::ShellExtensions>,
914 ) -> Result<Cow<'_, str>, error::Error> {
915 match self {
916 Self::Unset(_) => Ok("".into()),
917 Self::String(s) => match style {
918 FormatStyle::Basic => Ok(escape::quote_if_needed(
919 s.as_str(),
920 escape::QuoteMode::SingleQuote,
921 )),
922 FormatStyle::DeclarePrint => {
923 Ok(escape::force_quote(s.as_str(), escape::QuoteMode::DoubleQuote).into())
924 }
925 },
926 Self::AssociativeArray(values) => {
927 let mut result = String::new();
928 result.push('(');
929
930 for (key, value) in values {
931 let formatted_key =
932 escape::quote_if_needed(key.as_str(), escape::QuoteMode::DoubleQuote);
933 let formatted_value =
934 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
935
936 write!(result, "[{formatted_key}]={formatted_value} ")?;
941 }
942
943 result.push(')');
944 Ok(result.into())
945 }
946 Self::IndexedArray(values) => {
947 let mut result = String::new();
948 result.push('(');
949
950 for (i, (key, value)) in values.iter().enumerate() {
951 if i > 0 {
952 result.push(' ');
953 }
954
955 let formatted_value =
956 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
957 write!(result, "[{key}]={formatted_value}")?;
958 }
959
960 result.push(')');
961 Ok(result.into())
962 }
963 Self::Dynamic { getter, .. } => {
964 let dynamic_value = getter(shell);
965 let result = dynamic_value.format(style, shell)?.to_string();
966 Ok(result.into())
967 }
968 }
969 }
970
971 pub fn get_at(
983 &self,
984 index: &str,
985 shell: &Shell<impl extensions::ShellExtensions>,
986 ) -> Result<Option<Cow<'_, str>>, error::Error> {
987 match self {
988 Self::Unset(_) => Ok(None),
989 Self::String(s) => {
990 if index.parse::<u64>().unwrap_or(0) == 0 {
991 Ok(Some(Cow::Borrowed(s)))
992 } else {
993 Ok(None)
994 }
995 }
996 Self::AssociativeArray(values) => {
997 Ok(values.get(index).map(|s| Cow::Borrowed(s.as_str())))
998 }
999 Self::IndexedArray(values) => {
1000 let key = get_key_for_indexed_array(values, index)?;
1001 Ok(values.get(&key).map(|s| Cow::Borrowed(s.as_str())))
1002 }
1003 Self::Dynamic { getter, .. } => {
1004 let dynamic_value = getter(shell);
1005 let result = dynamic_value.get_at(index, shell)?;
1006 Ok(result.map(|s| s.to_string().into()))
1007 }
1008 }
1009 }
1010
1011 pub fn element_keys(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
1013 match self {
1014 Self::Unset(_) => vec![],
1015 Self::String(_) => vec!["0".to_owned()],
1016 Self::AssociativeArray(array) => array.keys().map(|k| k.to_owned()).collect(),
1017 Self::IndexedArray(array) => array.keys().map(|k| k.to_string()).collect(),
1018 Self::Dynamic { getter, .. } => getter(shell).element_keys(shell),
1019 }
1020 }
1021
1022 pub fn element_values(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
1024 match self {
1025 Self::Unset(_) => vec![],
1026 Self::String(s) => vec![s.to_owned()],
1027 Self::AssociativeArray(array) => array.values().map(|v| v.to_owned()).collect(),
1028 Self::IndexedArray(array) => array.values().map(|v| v.to_owned()).collect(),
1029 Self::Dynamic { getter, .. } => getter(shell).element_values(shell),
1030 }
1031 }
1032
1033 pub fn to_cow_str(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Cow<'_, str> {
1035 self.try_get_cow_str(shell).unwrap_or(Cow::Borrowed(""))
1036 }
1037
1038 fn to_cow_str_without_dynamic_support(&self) -> Cow<'_, str> {
1039 self.try_get_cow_str_without_dynamic_support()
1040 .unwrap_or(Cow::Borrowed(""))
1041 }
1042
1043 pub fn try_get_cow_str(
1046 &self,
1047 shell: &Shell<impl extensions::ShellExtensions>,
1048 ) -> Option<Cow<'_, str>> {
1049 match self {
1050 Self::Dynamic { getter, .. } => {
1051 let dynamic_value = getter(shell);
1052 dynamic_value
1053 .try_get_cow_str(shell)
1054 .map(|s| s.to_string().into())
1055 }
1056 _ => self.try_get_cow_str_without_dynamic_support(),
1057 }
1058 }
1059
1060 fn try_get_cow_str_without_dynamic_support(&self) -> Option<Cow<'_, str>> {
1061 match self {
1062 Self::Unset(_) => None,
1063 Self::String(s) => Some(Cow::Borrowed(s.as_str())),
1064 Self::AssociativeArray(values) => values.get("0").map(|s| Cow::Borrowed(s.as_str())),
1065 Self::IndexedArray(values) => values.get(&0).map(|s| Cow::Borrowed(s.as_str())),
1066 Self::Dynamic { .. } => None,
1067 }
1068 }
1069
1070 pub fn to_assignable_str(
1082 &self,
1083 index: Option<&str>,
1084 shell: &Shell<impl extensions::ShellExtensions>,
1085 ) -> Result<String, error::Error> {
1086 match self {
1087 Self::Unset(_) => Ok(String::new()),
1088 Self::String(s) => Ok(escape::force_quote(
1089 s.as_str(),
1090 escape::QuoteMode::SingleQuote,
1091 )),
1092 Self::AssociativeArray(_) | Self::IndexedArray(_) => {
1093 if let Some(index) = index {
1094 if let Ok(Some(value)) = self.get_at(index, shell) {
1095 Ok(escape::force_quote(
1096 value.as_ref(),
1097 escape::QuoteMode::SingleQuote,
1098 ))
1099 } else {
1100 Ok(String::new())
1101 }
1102 } else {
1103 Ok(self.format(FormatStyle::DeclarePrint, shell)?.into_owned())
1104 }
1105 }
1106 Self::Dynamic { getter, .. } => getter(shell).to_assignable_str(index, shell),
1107 }
1108 }
1109}
1110
1111fn get_key_for_indexed_array(
1112 values: &BTreeMap<u64, String>,
1113 index_str: &str,
1114) -> Result<u64, error::Error> {
1115 let mut index_value = index_str.parse::<i64>().unwrap_or(0);
1116
1117 #[expect(clippy::cast_possible_wrap)]
1119 if index_value < 0 {
1120 index_value += values.len() as i64;
1121 if index_value < 0 {
1122 return Err(error::ErrorKind::ArrayIndexOutOfRange(index_str.to_owned()).into());
1123 }
1124 }
1125
1126 #[expect(clippy::cast_sign_loss)]
1129 Ok(index_value as u64)
1130}
1131
1132impl From<&str> for ShellValue {
1133 fn from(value: &str) -> Self {
1134 Self::String(value.to_owned())
1135 }
1136}
1137
1138impl From<&String> for ShellValue {
1139 fn from(value: &String) -> Self {
1140 Self::String(value.clone())
1141 }
1142}
1143
1144impl From<String> for ShellValue {
1145 fn from(value: String) -> Self {
1146 Self::String(value)
1147 }
1148}
1149
1150impl From<OsString> for ShellValue {
1151 fn from(value: OsString) -> Self {
1152 Self::String(value.to_string_lossy().into_owned())
1153 }
1154}
1155
1156impl From<Vec<String>> for ShellValue {
1157 fn from(values: Vec<String>) -> Self {
1158 Self::indexed_array_from_strings(values)
1159 }
1160}
1161
1162impl From<Vec<&str>> for ShellValue {
1163 fn from(values: Vec<&str>) -> Self {
1164 Self::indexed_array_from_strs(values.as_slice())
1165 }
1166}