1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fmt::{Display, Write};
4
5use crate::shell::Shell;
6use crate::{error, escape};
7
8#[derive(Clone, Debug)]
10pub struct ShellVariable {
11 value: ShellValue,
13 exported: bool,
15 readonly: bool,
17 enumerable: bool,
19 transform_on_update: ShellVariableUpdateTransform,
21 trace: bool,
23 treat_as_integer: bool,
25 treat_as_nameref: bool,
27}
28
29#[derive(Clone, Debug)]
31pub enum ShellVariableUpdateTransform {
32 None,
34 Lowercase,
36 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 pub fn new(value: ShellValue) -> Self {
62 Self {
63 value,
64 ..ShellVariable::default()
65 }
66 }
67
68 pub fn value(&self) -> &ShellValue {
70 &self.value
71 }
72
73 pub fn is_exported(&self) -> bool {
75 self.exported
76 }
77
78 pub fn export(&mut self) -> &mut Self {
80 self.exported = true;
81 self
82 }
83
84 pub fn unexport(&mut self) -> &mut Self {
86 self.exported = false;
87 self
88 }
89
90 pub fn is_readonly(&self) -> bool {
92 self.readonly
93 }
94
95 pub fn set_readonly(&mut self) -> &mut Self {
97 self.readonly = true;
98 self
99 }
100
101 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 pub fn is_trace_enabled(&self) -> bool {
113 self.trace
114 }
115
116 pub fn enable_trace(&mut self) -> &mut Self {
118 self.trace = true;
119 self
120 }
121
122 pub fn disable_trace(&mut self) -> &mut Self {
124 self.trace = false;
125 self
126 }
127
128 pub fn is_enumerable(&self) -> bool {
130 self.enumerable
131 }
132
133 pub fn hide_from_enumeration(&mut self) -> &mut Self {
135 self.enumerable = false;
136 self
137 }
138
139 pub fn get_update_transform(&self) -> ShellVariableUpdateTransform {
141 self.transform_on_update.clone()
142 }
143
144 pub fn set_update_transform(&mut self, transform: ShellVariableUpdateTransform) {
146 self.transform_on_update = transform;
147 }
148
149 pub fn is_treated_as_integer(&self) -> bool {
151 self.treat_as_integer
152 }
153
154 pub fn treat_as_integer(&mut self) -> &mut Self {
156 self.treat_as_integer = true;
157 self
158 }
159
160 pub fn unset_treat_as_integer(&mut self) -> &mut Self {
162 self.treat_as_integer = false;
163 self
164 }
165
166 pub fn is_treated_as_nameref(&self) -> bool {
168 self.treat_as_nameref
169 }
170
171 pub fn treat_as_nameref(&mut self) -> &mut Self {
173 self.treat_as_nameref = true;
174 self
175 }
176
177 pub fn unset_treat_as_nameref(&mut self) -> &mut Self {
179 self.treat_as_nameref = false;
180 self
181 }
182
183 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 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 #[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 (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 (ShellValue::Unset(_), ShellValueLiteral::Scalar(_)) => {
252 self.assign(ShellValueLiteral::Scalar(String::new()), false)?;
253 }
254 (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 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 ShellValue::Dynamic { .. } => Ok(()),
305 }
306 } else {
307 match (&self.value, value) {
308 (
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 (
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 (
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 (ShellValue::Dynamic { .. }, _) => Ok(()),
349
350 (ShellValue::String(_) | ShellValue::Unset(_), ShellValueLiteral::Scalar(s)) => {
352 self.value = ShellValue::String(s);
353 Ok(())
354 }
355 }
356 }
357 }
358
359 #[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 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 pub(crate) fn resolve_value(&self, shell: &Shell) -> ShellValue {
494 match &self.value {
496 ShellValue::Dynamic { getter, .. } => getter(shell),
497 _ => self.value.clone(),
498 }
499 }
500
501 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#[derive(Clone, Debug)]
551pub enum ShellValue {
552 Unset(ShellValueUnsetType),
554 String(String),
556 AssociativeArray(BTreeMap<String, String>),
558 IndexedArray(BTreeMap<u64, String>),
560 Dynamic {
562 getter: DynamicValueGetter,
564 setter: DynamicValueSetter,
566 },
567}
568
569#[derive(Clone, Debug)]
571pub enum ShellValueUnsetType {
572 Untyped,
574 AssociativeArray,
576 IndexedArray,
578}
579
580#[derive(Clone, Debug)]
582pub enum ShellValueLiteral {
583 Scalar(String),
585 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#[derive(Clone, Debug)]
645pub struct ArrayLiteral(pub Vec<(Option<String>, String)>);
646
647#[derive(Copy, Clone, Debug)]
649pub enum FormatStyle {
650 Basic,
652 DeclarePrint,
654}
655
656impl ShellValue {
657 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 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 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 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 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 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 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 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 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 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 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 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 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}