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> {
111 if self.readonly {
112 return Err(error::ErrorKind::ReadonlyVariable.into());
113 }
114
115 self.readonly = false;
116 Ok(self)
117 }
118
119 pub const fn is_trace_enabled(&self) -> bool {
121 self.trace
122 }
123
124 pub const fn enable_trace(&mut self) -> &mut Self {
126 self.trace = true;
127 self
128 }
129
130 pub const fn disable_trace(&mut self) -> &mut Self {
132 self.trace = false;
133 self
134 }
135
136 pub const fn is_enumerable(&self) -> bool {
139 self.enumerable
140 }
141
142 pub const fn hide_from_enumeration(&mut self) -> &mut Self {
144 self.enumerable = false;
145 self
146 }
147
148 pub const fn get_update_transform(&self) -> ShellVariableUpdateTransform {
150 self.transform_on_update
151 }
152
153 pub const fn set_update_transform(&mut self, transform: ShellVariableUpdateTransform) {
155 self.transform_on_update = transform;
156 }
157
158 pub const fn is_treated_as_integer(&self) -> bool {
160 self.treat_as_integer
161 }
162
163 pub const fn treat_as_integer(&mut self) -> &mut Self {
165 self.treat_as_integer = true;
166 self
167 }
168
169 pub const fn unset_treat_as_integer(&mut self) -> &mut Self {
171 self.treat_as_integer = false;
172 self
173 }
174
175 pub const fn is_treated_as_nameref(&self) -> bool {
178 self.treat_as_nameref
179 }
180
181 pub const fn treat_as_nameref(&mut self) -> &mut Self {
183 self.treat_as_nameref = true;
184 self
185 }
186
187 pub const fn unset_treat_as_nameref(&mut self) -> &mut Self {
189 self.treat_as_nameref = false;
190 self
191 }
192
193 pub fn convert_to_indexed_array(&mut self) -> Result<(), error::Error> {
195 match self.value() {
196 ShellValue::IndexedArray(_) => Ok(()),
197 ShellValue::AssociativeArray(_) => {
198 Err(error::ErrorKind::ConvertingAssociativeArrayToIndexedArray.into())
199 }
200 _ => {
201 let mut new_values = BTreeMap::new();
202 new_values.insert(
203 0,
204 self.value.to_cow_str_without_dynamic_support().to_string(),
205 );
206 self.value = ShellValue::IndexedArray(new_values);
207 Ok(())
208 }
209 }
210 }
211
212 pub fn convert_to_associative_array(&mut self) -> Result<(), error::Error> {
214 match self.value() {
215 ShellValue::AssociativeArray(_) => Ok(()),
216 ShellValue::IndexedArray(_) => {
217 Err(error::ErrorKind::ConvertingIndexedArrayToAssociativeArray.into())
218 }
219 _ => {
220 let mut new_values: BTreeMap<String, String> = BTreeMap::new();
221 new_values.insert(
222 String::from("0"),
223 self.value.to_cow_str_without_dynamic_support().to_string(),
224 );
225 self.value = ShellValue::AssociativeArray(new_values);
226 Ok(())
227 }
228 }
229 }
230
231 #[expect(clippy::too_many_lines)]
240 pub fn assign(&mut self, value: ShellValueLiteral, append: bool) -> Result<(), error::Error> {
241 if self.is_readonly() {
242 return Err(error::ErrorKind::ReadonlyVariable.into());
243 }
244
245 let value = self.convert_value_literal_for_assignment(value);
246
247 if append {
248 match (&self.value, &value) {
249 (ShellValue::Unset(_), ShellValueLiteral::Array(_))
252 | (
253 ShellValue::Unset(
254 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::AssociativeArray,
255 ),
256 _,
257 ) => {
258 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
259 }
260 (ShellValue::Unset(_), ShellValueLiteral::Scalar(_)) => {
264 self.assign(ShellValueLiteral::Scalar(String::new()), false)?;
265 }
266 (ShellValue::String(_), ShellValueLiteral::Array(_)) => {
269 self.convert_to_indexed_array()?;
270 }
271 _ => (),
272 }
273
274 let treat_as_int = self.is_treated_as_integer();
275 let update_transform = self.get_update_transform();
276
277 match &mut self.value {
278 ShellValue::String(base) => match value {
279 ShellValueLiteral::Scalar(suffix) => {
280 if treat_as_int {
281 let int_value = base.parse::<i64>().unwrap_or(0)
282 + suffix.parse::<i64>().unwrap_or(0);
283 base.clear();
284 base.push_str(int_value.to_string().as_str());
285 } else {
286 base.push_str(suffix.as_str());
287 Self::apply_value_transforms(base, treat_as_int, update_transform);
288 }
289 Ok(())
290 }
291 ShellValueLiteral::Array(_) => {
292 Ok(())
294 }
295 },
296 ShellValue::IndexedArray(existing_values) => match value {
297 ShellValueLiteral::Scalar(new_value) => {
298 self.assign_at_index(String::from("0"), new_value, append)
299 }
300 ShellValueLiteral::Array(new_values) => {
301 ShellValue::update_indexed_array_from_literals(existing_values, new_values);
302 Ok(())
303 }
304 },
305 ShellValue::AssociativeArray(existing_values) => match value {
306 ShellValueLiteral::Scalar(new_value) => {
307 self.assign_at_index(String::from("0"), new_value, append)
308 }
309 ShellValueLiteral::Array(new_values) => {
310 ShellValue::update_associative_array_from_literals(
311 existing_values,
312 new_values,
313 )
314 }
315 },
316 ShellValue::Unset(_) => unreachable!("covered in conversion above"),
317 ShellValue::Dynamic { .. } => Ok(()),
319 }
320 } else {
321 match (&self.value, value) {
322 (
325 ShellValue::IndexedArray(_)
326 | ShellValue::AssociativeArray(_)
327 | ShellValue::Unset(
328 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray,
329 ),
330 ShellValueLiteral::Scalar(s),
331 ) => self.assign_at_index(String::from("0"), s, false),
332
333 (
337 ShellValue::IndexedArray(_)
338 | ShellValue::Unset(
339 ShellValueUnsetType::IndexedArray | ShellValueUnsetType::Untyped,
340 )
341 | ShellValue::String(_)
342 | ShellValue::Dynamic { .. },
343 ShellValueLiteral::Array(literal_values),
344 ) => {
345 self.value = ShellValue::indexed_array_from_literals(literal_values);
346 Ok(())
347 }
348
349 (
352 ShellValue::AssociativeArray(_)
353 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray),
354 ShellValueLiteral::Array(literal_values),
355 ) => {
356 self.value = ShellValue::associative_array_from_literals(literal_values)?;
357 Ok(())
358 }
359
360 (ShellValue::Dynamic { .. }, _) => Ok(()),
363
364 (ShellValue::String(_) | ShellValue::Unset(_), ShellValueLiteral::Scalar(s)) => {
366 self.value = ShellValue::String(s);
367 Ok(())
368 }
369 }
370 }
371 }
372
373 pub fn assign_at_index(
384 &mut self,
385 array_index: String,
386 value: String,
387 append: bool,
388 ) -> Result<(), error::Error> {
389 match &self.value {
390 ShellValue::Unset(_) => {
391 self.assign(ShellValueLiteral::Array(ArrayLiteral(vec![])), false)?;
392 }
393 ShellValue::String(_) => {
394 self.convert_to_indexed_array()?;
395 }
396 _ => (),
397 }
398
399 let treat_as_int = self.is_treated_as_integer();
400 let value = self.convert_value_str_for_assignment(value);
401
402 match &mut self.value {
403 ShellValue::IndexedArray(arr) => {
404 let key = get_key_for_indexed_array(arr, array_index.as_str())?;
405
406 if append {
407 let existing_value = arr.get(&key).map_or("", |v| v.as_str());
408
409 let mut new_value;
410 if treat_as_int {
411 new_value = (existing_value.parse::<i64>().unwrap_or(0)
412 + value.parse::<i64>().unwrap_or(0))
413 .to_string();
414 } else {
415 new_value = existing_value.to_owned();
416 new_value.push_str(value.as_str());
417 }
418
419 arr.insert(key, new_value);
420 } else {
421 arr.insert(key, value);
422 }
423
424 Ok(())
425 }
426 ShellValue::AssociativeArray(arr) => {
427 if append {
428 let existing_value = arr.get(array_index.as_str()).map_or("", |v| v.as_str());
429
430 let mut new_value;
431 if treat_as_int {
432 new_value = (existing_value.parse::<i64>().unwrap_or(0)
433 + value.parse::<i64>().unwrap_or(0))
434 .to_string();
435 } else {
436 new_value = existing_value.to_owned();
437 new_value.push_str(value.as_str());
438 }
439
440 arr.insert(array_index, new_value.clone());
441 } else {
442 arr.insert(array_index, value);
443 }
444 Ok(())
445 }
446 _ => {
447 tracing::error!("assigning to index {array_index} of {:?}", self.value);
448 error::unimp("assigning to index of non-array variable")
449 }
450 }
451 }
452
453 fn convert_value_literal_for_assignment(&self, value: ShellValueLiteral) -> ShellValueLiteral {
454 match value {
455 ShellValueLiteral::Scalar(s) => {
456 ShellValueLiteral::Scalar(self.convert_value_str_for_assignment(s))
457 }
458 ShellValueLiteral::Array(literals) => ShellValueLiteral::Array(ArrayLiteral(
459 literals
460 .0
461 .into_iter()
462 .map(|(k, v)| (k, self.convert_value_str_for_assignment(v)))
463 .collect(),
464 )),
465 }
466 }
467
468 fn convert_value_str_for_assignment(&self, mut s: String) -> String {
469 Self::apply_value_transforms(
470 &mut s,
471 self.is_treated_as_integer(),
472 self.get_update_transform(),
473 );
474
475 s
476 }
477
478 fn apply_value_transforms(
479 s: &mut String,
480 treat_as_int: bool,
481 update_transform: ShellVariableUpdateTransform,
482 ) {
483 if treat_as_int {
484 *s = (*s).parse::<i64>().unwrap_or(0).to_string();
485 } else {
486 match update_transform {
487 ShellVariableUpdateTransform::None => (),
488 ShellVariableUpdateTransform::Lowercase => *s = (*s).to_lowercase(),
489 ShellVariableUpdateTransform::Uppercase => *s = (*s).to_uppercase(),
490 ShellVariableUpdateTransform::Capitalize => {
491 *s = s.to_lowercase();
494 if let Some(c) = s.chars().next() {
495 s.replace_range(0..1, &c.to_uppercase().to_string());
496 }
497 }
498 }
499 }
500 }
501
502 pub fn unset_index(&mut self, index: &str) -> Result<bool, error::Error> {
509 match &mut self.value {
510 ShellValue::Unset(ty) => match ty {
511 ShellValueUnsetType::Untyped => Err(error::ErrorKind::NotArray.into()),
512 ShellValueUnsetType::AssociativeArray | ShellValueUnsetType::IndexedArray => {
513 Ok(false)
514 }
515 },
516 ShellValue::String(_) => Err(error::ErrorKind::NotArray.into()),
517 ShellValue::AssociativeArray(values) => Ok(values.remove(index).is_some()),
518 ShellValue::IndexedArray(values) => {
519 let key = get_key_for_indexed_array(values, index)?;
520 Ok(values.remove(&key).is_some())
521 }
522 ShellValue::Dynamic { .. } => Ok(false),
523 }
524 }
525
526 pub fn resolve_value(&self, shell: &Shell<impl extensions::ShellExtensions>) -> ShellValue {
533 match &self.value {
536 ShellValue::Dynamic { getter, .. } => getter(shell),
537 _ => self.value.clone(),
538 }
539 }
540
541 pub fn attribute_flags(&self, shell: &Shell<impl extensions::ShellExtensions>) -> String {
543 let value = self.resolve_value(shell);
544
545 let mut result = String::new();
546
547 if value.is_indexed_array() {
548 result.push('a');
549 }
550 if value.is_associative_array() {
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_indexed_array(&self) -> bool {
727 matches!(
728 self,
729 Self::IndexedArray(_) | Self::Unset(ShellValueUnsetType::IndexedArray)
730 )
731 }
732
733 pub const fn is_associative_array(&self) -> bool {
736 matches!(
737 self,
738 Self::AssociativeArray(_) | Self::Unset(ShellValueUnsetType::AssociativeArray)
739 )
740 }
741
742 pub const fn is_array(&self) -> bool {
744 self.is_indexed_array() || self.is_associative_array()
745 }
746
747 pub const fn is_set(&self) -> bool {
749 !matches!(self, Self::Unset(_))
750 }
751
752 pub fn indexed_array_from_strings<S>(values: S) -> Self
759 where
760 S: IntoIterator<Item = String>,
761 {
762 let mut owned_values = BTreeMap::new();
763 for (i, value) in values.into_iter().enumerate() {
764 owned_values.insert(i as u64, value);
765 }
766
767 Self::IndexedArray(owned_values)
768 }
769
770 pub fn indexed_array_from_strs(values: &[&str]) -> Self {
777 let mut owned_values = BTreeMap::new();
778 for (i, value) in values.iter().enumerate() {
779 owned_values.insert(i as u64, (*value).to_string());
780 }
781
782 Self::IndexedArray(owned_values)
783 }
784
785 pub fn indexed_array_from_literals(literals: ArrayLiteral) -> Self {
791 let mut values = BTreeMap::new();
792 Self::update_indexed_array_from_literals(&mut values, literals);
793
794 Self::IndexedArray(values)
795 }
796
797 fn update_indexed_array_from_literals(
798 existing_values: &mut BTreeMap<u64, String>,
799 literal_values: ArrayLiteral,
800 ) {
801 let mut new_key = if let Some((largest_index, _)) = existing_values.last_key_value() {
802 largest_index + 1
803 } else {
804 0
805 };
806
807 for (key, value) in literal_values.0 {
808 if let Some(key) = key {
809 new_key = key.parse().unwrap_or(0);
810 }
811
812 existing_values.insert(new_key, value);
813 new_key += 1;
814 }
815 }
816
817 pub fn associative_array_from_literals(literals: ArrayLiteral) -> Result<Self, error::Error> {
824 let mut values = BTreeMap::new();
825 Self::update_associative_array_from_literals(&mut values, literals)?;
826
827 Ok(Self::AssociativeArray(values))
828 }
829
830 fn update_associative_array_from_literals(
831 existing_values: &mut BTreeMap<String, String>,
832 literal_values: ArrayLiteral,
833 ) -> Result<(), error::Error> {
834 let mut current_key = None;
835 for (key, value) in literal_values.0 {
836 if let Some(current_key) = current_key.take() {
837 if key.is_some() {
838 return error::unimp("misaligned keys/values in associative array literal");
839 } else {
840 existing_values.insert(current_key, value);
841 }
842 } else if let Some(key) = key {
843 existing_values.insert(key, value);
844 } else {
845 current_key = Some(value);
846 }
847 }
848
849 if let Some(current_key) = current_key {
850 existing_values.insert(current_key, String::new());
851 }
852
853 Ok(())
854 }
855
856 pub fn format(
862 &self,
863 style: FormatStyle,
864 shell: &Shell<impl extensions::ShellExtensions>,
865 ) -> Result<Cow<'_, str>, error::Error> {
866 match self {
867 Self::Unset(_) => Ok("".into()),
868 Self::String(s) => match style {
869 FormatStyle::Basic => Ok(escape::quote_if_needed(
870 s.as_str(),
871 escape::QuoteMode::SingleQuote,
872 )),
873 FormatStyle::DeclarePrint => {
874 Ok(escape::force_quote(s.as_str(), escape::QuoteMode::DoubleQuote).into())
875 }
876 },
877 Self::AssociativeArray(values) => {
878 let mut result = String::new();
879 result.push('(');
880
881 for (key, value) in values {
882 let formatted_key =
883 escape::quote_if_needed(key.as_str(), escape::QuoteMode::DoubleQuote);
884 let formatted_value =
885 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
886
887 write!(result, "[{formatted_key}]={formatted_value} ")?;
892 }
893
894 result.push(')');
895 Ok(result.into())
896 }
897 Self::IndexedArray(values) => {
898 let mut result = String::new();
899 result.push('(');
900
901 for (i, (key, value)) in values.iter().enumerate() {
902 if i > 0 {
903 result.push(' ');
904 }
905
906 let formatted_value =
907 escape::force_quote(value.as_str(), escape::QuoteMode::DoubleQuote);
908 write!(result, "[{key}]={formatted_value}")?;
909 }
910
911 result.push(')');
912 Ok(result.into())
913 }
914 Self::Dynamic { getter, .. } => {
915 let dynamic_value = getter(shell);
916 let result = dynamic_value.format(style, shell)?.to_string();
917 Ok(result.into())
918 }
919 }
920 }
921
922 pub fn get_at(
928 &self,
929 index: &str,
930 shell: &Shell<impl extensions::ShellExtensions>,
931 ) -> Result<Option<Cow<'_, str>>, error::Error> {
932 match self {
933 Self::Unset(_) => Ok(None),
934 Self::String(s) => {
935 if index.parse::<u64>().unwrap_or(0) == 0 {
936 Ok(Some(Cow::Borrowed(s)))
937 } else {
938 Ok(None)
939 }
940 }
941 Self::AssociativeArray(values) => {
942 Ok(values.get(index).map(|s| Cow::Borrowed(s.as_str())))
943 }
944 Self::IndexedArray(values) => {
945 let key = get_key_for_indexed_array(values, index)?;
946 Ok(values.get(&key).map(|s| Cow::Borrowed(s.as_str())))
947 }
948 Self::Dynamic { getter, .. } => {
949 let dynamic_value = getter(shell);
950 let result = dynamic_value.get_at(index, shell)?;
951 Ok(result.map(|s| s.to_string().into()))
952 }
953 }
954 }
955
956 pub fn element_keys(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
958 match self {
959 Self::Unset(_) => vec![],
960 Self::String(_) => vec!["0".to_owned()],
961 Self::AssociativeArray(array) => array.keys().map(|k| k.to_owned()).collect(),
962 Self::IndexedArray(array) => array.keys().map(|k| k.to_string()).collect(),
963 Self::Dynamic { getter, .. } => getter(shell).element_keys(shell),
964 }
965 }
966
967 pub fn element_values(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Vec<String> {
969 match self {
970 Self::Unset(_) => vec![],
971 Self::String(s) => vec![s.to_owned()],
972 Self::AssociativeArray(array) => array.values().map(|v| v.to_owned()).collect(),
973 Self::IndexedArray(array) => array.values().map(|v| v.to_owned()).collect(),
974 Self::Dynamic { getter, .. } => getter(shell).element_values(shell),
975 }
976 }
977
978 pub fn to_cow_str(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Cow<'_, str> {
980 self.try_get_cow_str(shell).unwrap_or(Cow::Borrowed(""))
981 }
982
983 fn to_cow_str_without_dynamic_support(&self) -> Cow<'_, str> {
984 self.try_get_cow_str_without_dynamic_support()
985 .unwrap_or(Cow::Borrowed(""))
986 }
987
988 pub fn try_get_cow_str(
991 &self,
992 shell: &Shell<impl extensions::ShellExtensions>,
993 ) -> Option<Cow<'_, str>> {
994 match self {
995 Self::Dynamic { getter, .. } => {
996 let dynamic_value = getter(shell);
997 dynamic_value
998 .try_get_cow_str(shell)
999 .map(|s| s.to_string().into())
1000 }
1001 _ => self.try_get_cow_str_without_dynamic_support(),
1002 }
1003 }
1004
1005 fn try_get_cow_str_without_dynamic_support(&self) -> Option<Cow<'_, str>> {
1006 match self {
1007 Self::Unset(_) => None,
1008 Self::String(s) => Some(Cow::Borrowed(s.as_str())),
1009 Self::AssociativeArray(values) => values.get("0").map(|s| Cow::Borrowed(s.as_str())),
1010 Self::IndexedArray(values) => values.get(&0).map(|s| Cow::Borrowed(s.as_str())),
1011 Self::Dynamic { .. } => None,
1012 }
1013 }
1014
1015 pub fn to_assignable_str(
1022 &self,
1023 index: Option<&str>,
1024 shell: &Shell<impl extensions::ShellExtensions>,
1025 ) -> Result<String, error::Error> {
1026 match self {
1027 Self::Unset(_) => Ok(String::new()),
1028 Self::String(s) => Ok(escape::force_quote(
1029 s.as_str(),
1030 escape::QuoteMode::SingleQuote,
1031 )),
1032 Self::AssociativeArray(_) | Self::IndexedArray(_) => {
1033 if let Some(index) = index {
1034 if let Ok(Some(value)) = self.get_at(index, shell) {
1035 Ok(escape::force_quote(
1036 value.as_ref(),
1037 escape::QuoteMode::SingleQuote,
1038 ))
1039 } else {
1040 Ok(String::new())
1041 }
1042 } else {
1043 Ok(self.format(FormatStyle::DeclarePrint, shell)?.into_owned())
1044 }
1045 }
1046 Self::Dynamic { getter, .. } => getter(shell).to_assignable_str(index, shell),
1047 }
1048 }
1049}
1050
1051fn get_key_for_indexed_array(
1052 values: &BTreeMap<u64, String>,
1053 index_str: &str,
1054) -> Result<u64, error::Error> {
1055 let mut index_value = index_str.parse::<i64>().unwrap_or(0);
1056
1057 #[expect(clippy::cast_possible_wrap)]
1059 if index_value < 0 {
1060 index_value += values.len() as i64;
1061 if index_value < 0 {
1062 return Err(error::ErrorKind::ArrayIndexOutOfRange(index_str.to_owned()).into());
1063 }
1064 }
1065
1066 #[expect(clippy::cast_sign_loss)]
1069 Ok(index_value as u64)
1070}
1071
1072impl From<&str> for ShellValue {
1073 fn from(value: &str) -> Self {
1074 Self::String(value.to_owned())
1075 }
1076}
1077
1078impl From<&String> for ShellValue {
1079 fn from(value: &String) -> Self {
1080 Self::String(value.clone())
1081 }
1082}
1083
1084impl From<String> for ShellValue {
1085 fn from(value: String) -> Self {
1086 Self::String(value)
1087 }
1088}
1089
1090impl From<OsString> for ShellValue {
1091 fn from(value: OsString) -> Self {
1092 Self::String(value.to_string_lossy().into_owned())
1093 }
1094}
1095
1096impl From<Vec<String>> for ShellValue {
1097 fn from(values: Vec<String>) -> Self {
1098 Self::indexed_array_from_strings(values)
1099 }
1100}
1101
1102impl From<Vec<&str>> for ShellValue {
1103 fn from(values: Vec<&str>) -> Self {
1104 Self::indexed_array_from_strs(values.as_slice())
1105 }
1106}