yerba 0.4.2

YAML Editing and Refactoring with Better Accuracy
use super::*;

impl Document {
  pub fn filter(&self, dot_path: &str, condition: &str) -> Vec<serde_yaml::Value> {
    self
      .navigate_all(dot_path)
      .iter()
      .filter(|node| self.evaluate_condition_on_node(node, condition))
      .map(node_to_yaml_value)
      .collect()
  }

  pub(super) fn evaluate_condition_on_node(&self, node: &SyntaxNode, condition: &str) -> bool {
    let condition = condition.trim();

    let (left, operator, right) = match parse_condition(condition) {
      Some(parts) => parts,
      None => return false,
    };

    let path = crate::selector::Selector::parse(&left);

    if !path.is_relative() {
      return false;
    }

    let target_nodes = navigate_from_node(node, &path.to_selector_string());
    let values: Vec<String> = target_nodes.iter().filter_map(extract_scalar_text).collect();

    match operator {
      "==" => values.iter().any(|value| value == &right),
      "!=" => values.iter().all(|value| value != &right),
      "contains" => {
        if values.iter().any(|value| value == &right || value.contains(&right)) {
          return true;
        }

        for node in &target_nodes {
          if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
            for entry in sequence.entries() {
              if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
                if text == right {
                  return true;
                }
              }
            }
          }
        }

        false
      }
      "not_contains" => {
        for node in &target_nodes {
          if let Some(sequence) = node.descendants().find_map(BlockSeq::cast) {
            for entry in sequence.entries() {
              if let Some(text) = entry.flow().and_then(|flow| extract_scalar_text(flow.syntax())) {
                if text == right {
                  return false;
                }
              }
            }
          }
        }

        !values.iter().any(|value| value == &right || value.contains(&right))
      }
      _ => false,
    }
  }

  pub fn evaluate_condition(&self, parent_path: &str, condition: &str) -> bool {
    let condition = condition.trim();

    let (left, operator, right) = match parse_condition(condition) {
      Some(parts) => parts,
      None => return false,
    };

    let path = crate::selector::Selector::parse(&left);

    let full_path = if path.is_relative() {
      let path_string = path.to_selector_string();

      if parent_path.is_empty() {
        path_string
      } else {
        format!("{}.{}", parent_path, path_string)
      }
    } else {
      path.to_selector_string()
    };

    let has_brackets = crate::selector::Selector::parse(&full_path).has_brackets();

    match operator {
      "==" => {
        if has_brackets {
          self.get_all(&full_path).iter().any(|value| value == &right)
        } else {
          self.get(&full_path).unwrap_or_default() == right
        }
      }
      "!=" => {
        if has_brackets {
          self.get_all(&full_path).iter().all(|value| value != &right)
        } else {
          self.get(&full_path).unwrap_or_default() != right
        }
      }
      "contains" => {
        if has_brackets {
          self.get_all(&full_path).iter().any(|value| value == &right || value.contains(&right))
        } else {
          let items = self.get_sequence_values(&full_path);

          if !items.is_empty() {
            items.iter().any(|item| item == &right)
          } else {
            self.get(&full_path).map(|value| value.contains(&right)).unwrap_or(false)
          }
        }
      }
      "not_contains" => {
        if has_brackets {
          self.get_all(&full_path).iter().all(|value| value != &right && !value.contains(&right))
        } else {
          let items = self.get_sequence_values(&full_path);

          if !items.is_empty() {
            !items.iter().any(|item| item == &right)
          } else {
            self.get(&full_path).map(|value| !value.contains(&right)).unwrap_or(true)
          }
        }
      }
      _ => false,
    }
  }
}