just 1.54.0

🤖 Just a command runner
Documentation
use super::*;

#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub(crate) struct Value {
  elements: Vec<String>,
}

impl Value {
  pub(crate) fn new() -> Self {
    Self::default()
  }

  pub(crate) fn elements(&self) -> &[String] {
    &self.elements
  }

  pub(crate) fn push(&mut self, element: &str) {
    self.elements.push(element.into());
  }

  pub(crate) fn join(&self) -> String {
    self.elements.join(" ")
  }

  pub(crate) fn apply<'src>(
    &self,
    other: &Self,
    operator: ListOperator,
    token: Token<'src>,
  ) -> RunResult<'src, Self> {
    let separator = operator.separator();
    let (a, b) = (&self.elements, &other.elements);
    match (a.len(), b.len()) {
      (1, 1..) => Ok(
        b.iter()
          .map(|b| format!("{}{separator}{b}", a[0]))
          .collect(),
      ),
      (1.., 1) => Ok(
        a.iter()
          .map(|a| format!("{a}{separator}{}", b[0]))
          .collect(),
      ),
      (m, n) if m == n => Ok(
        a.iter()
          .zip(b)
          .map(|(a, b)| format!("{a}{separator}{b}"))
          .collect(),
      ),
      _ => Err(Error::ListOperation {
        lhs: self.clone(),
        operator,
        rhs: other.clone(),
        token: Box::new(token),
      }),
    }
  }

  pub(crate) fn into_elements(self) -> Vec<String> {
    self.elements
  }

  pub(crate) fn is_empty(&self) -> bool {
    self.elements.is_empty()
  }

  pub(crate) fn is_truthy(&self) -> bool {
    !self.elements.is_empty()
  }
}

impl ColorDisplay for Value {
  fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
    if self.elements.len() == 1 {
      write!(f, "{}", Element(&self.elements[0]).color_display(color))
    } else {
      write!(f, "[")?;

      for (i, element) in self.elements.iter().enumerate() {
        if i > 0 {
          write!(f, ", ")?;
        }

        write!(f, "{}", Element(element).color_display(color))?;
      }

      write!(f, "]")
    }
  }
}

impl From<&String> for Value {
  fn from(element: &String) -> Self {
    Self {
      elements: vec![element.clone()],
    }
  }
}

impl From<bool> for Value {
  fn from(condition: bool) -> Self {
    if condition {
      "true".into()
    } else {
      Self::new()
    }
  }
}

impl From<&str> for Value {
  fn from(element: &str) -> Self {
    element.to_string().into()
  }
}

impl From<String> for Value {
  fn from(element: String) -> Self {
    Self {
      elements: vec![element],
    }
  }
}

impl From<Vec<String>> for Value {
  fn from(elements: Vec<String>) -> Self {
    Self { elements }
  }
}

impl FromIterator<String> for Value {
  fn from_iter<I: IntoIterator<Item = String>>(elements: I) -> Self {
    Self {
      elements: elements.into_iter().collect(),
    }
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn join() {
    #[track_caller]
    fn case(elements: &[&str], expected: &str) {
      let value = elements.iter().map(ToString::to_string).collect::<Value>();
      assert_eq!(value.join(), expected);
    }

    case(&[], "");
    case(&[""], "");
    case(&["foo"], "foo");
    case(&["foo", "bar"], "foo bar");
    case(&["foo bar", "baz"], "foo bar baz");
    case(&["", ""], " ");
  }

  #[test]
  fn display() {
    #[track_caller]
    fn case(elements: &[&str], expected: &str) {
      let value = elements.iter().map(ToString::to_string).collect::<Value>();
      assert_eq!(value.color_display(Color::never()).to_string(), expected);
    }

    case(&[], "[]");
    case(&["foo"], r#""foo""#);
    case(&["foo", "bar"], r#"["foo", "bar"]"#);
    case(&["a\tb\"c"], r#""a\tb\"c""#);
    case(&["\\", "\n"], r#"["\\", "\n"]"#);
  }

  #[test]
  fn color_display() {
    assert_eq!(
      Value::from("abc\t\r\nxyz")
        .color_display(Color::always())
        .to_string(),
      "\u{1b}[32m\"abc\u{1b}[36m\\t\\r\\n\u{1b}[32mxyz\"\u{1b}[0m",
    );
  }

  #[test]
  fn is_truthy() {
    #[track_caller]
    fn case(elements: &[&str], expected: bool) {
      let value = elements.iter().map(ToString::to_string).collect::<Value>();
      assert_eq!(value.is_truthy(), expected);
    }

    case(&[], false);
    case(&[""], true);
    case(&["foo"], true);
    case(&["", ""], true);
    case(&["foo", "bar"], true);
  }

  #[test]
  fn from_str() {
    assert_eq!(Value::from("foo bar").elements(), ["foo bar"]);
    assert_eq!(Value::from(String::from("foo")).elements(), ["foo"]);
    assert_eq!(Value::new().elements(), [] as [&str; 0]);
  }

  #[test]
  fn apply() {
    use ListOperator::{Concatenate, Join};

    #[track_caller]
    fn case(a: &[&str], b: &[&str], operator: ListOperator, expected: Option<&[&str]>) {
      let a = a.iter().map(ToString::to_string).collect::<Value>();
      let b = b.iter().map(ToString::to_string).collect::<Value>();
      let expected = expected.map(|expected| expected.iter().map(ToString::to_string).collect());
      assert_eq!(
        a.apply(
          &b,
          operator,
          Token {
            column: 0,
            kind: TokenKind::Plus,
            length: 0,
            line: 0,
            offset: 0,
            path: Path::new(""),
            src: "",
          }
        )
        .ok(),
        expected
      );
    }

    case(&[], &[], Concatenate, Some(&[]));
    case(&["a"], &["b"], Concatenate, Some(&["ab"]));
    case(&["a"], &["b"], Join, Some(&["a/b"]));
    case(&["a", "b"], &["c", "d"], Concatenate, Some(&["ac", "bd"]));
    case(&["a", "b"], &["c", "d"], Join, Some(&["a/c", "b/d"]));
    case(&["a"], &["b", "c"], Concatenate, Some(&["ab", "ac"]));
    case(
      &["a"],
      &["b", "c", "d"],
      Concatenate,
      Some(&["ab", "ac", "ad"]),
    );
    case(&["a", "b"], &["c"], Join, Some(&["a/c", "b/c"]));
    case(
      &["a", "b", "c"],
      &["d"],
      Concatenate,
      Some(&["ad", "bd", "cd"]),
    );

    case(&[], &["a"], Concatenate, None);
    case(&["a"], &[], Concatenate, None);
    case(&[], &["a", "b"], Concatenate, None);
    case(&["a", "b"], &[], Concatenate, None);
    case(&["a", "b"], &["c", "d", "e"], Concatenate, None);
  }
}