yerba 0.6.0

YAML Editing and Refactoring with Better Accuracy
use serde_json::Value;

use crate::QuoteStyle;

pub fn json_to_yaml_text(value: &Value, quote_style: &QuoteStyle, indent: usize) -> String {
  match value {
    Value::Object(map) => {
      let prefix = " ".repeat(indent);

      map
        .iter()
        .map(|(k, v)| match v {
          Value::Array(arr) => {
            let items: Vec<String> = arr
              .iter()
              .map(|item| match item {
                Value::Object(_) => {
                  let inner = json_to_yaml_text(item, quote_style, indent + 4);
                  format!("{}  - {}", prefix, inner.trim_start())
                }

                _ => format!("{}  - {}", prefix, format_yaml_scalar(item, quote_style)),
              })
              .collect();

            format!("{}{}:\n{}", prefix, k, items.join("\n"))
          }

          Value::Object(_) => {
            let inner = json_to_yaml_text(v, quote_style, indent + 2);
            format!("{}{}:\n{}", prefix, k, inner)
          }

          _ => format!("{}{}: {}", prefix, k, format_yaml_scalar(v, quote_style)),
        })
        .collect::<Vec<_>>()
        .join("\n")
    }

    Value::Array(array) => {
      let prefix = " ".repeat(indent);

      array
        .iter()
        .map(|item| match item {
          Value::Object(_) => {
            let inner = json_to_yaml_text(item, quote_style, indent + 2);
            format!("{}- {}", prefix, inner.trim_start())
          }

          _ => format!("{}- {}", prefix, format_yaml_scalar(item, quote_style)),
        })
        .collect::<Vec<_>>()
        .join("\n")
    }

    _ => {
      let prefix = " ".repeat(indent);

      format!("{}{}", prefix, format_yaml_scalar(value, quote_style))
    }
  }
}

pub fn yaml_value_to_flow_text(value: &yaml_serde::Value) -> String {
  match value {
    yaml_serde::Value::Null => "null".to_string(),
    yaml_serde::Value::Bool(boolean) => boolean.to_string(),
    yaml_serde::Value::Number(number) => number.to_string(),

    yaml_serde::Value::String(string) => {
      if crate::syntax::is_yaml_non_string(string) {
        format!("\"{}\"", string.replace('"', "\\\""))
      } else {
        string.clone()
      }
    }

    yaml_serde::Value::Sequence(sequence) => {
      let items: Vec<String> = sequence.iter().map(yaml_value_to_flow_text).collect();

      format!("[{}]", items.join(", "))
    }

    yaml_serde::Value::Mapping(mapping) => {
      let pairs: Vec<String> = mapping
        .iter()
        .map(|(key, value)| {
          let key_string = match key {
            yaml_serde::Value::String(string) => string.clone(),
            _ => format!("{:?}", key),
          };

          format!("{}: {}", key_string, yaml_value_to_flow_text(value))
        })
        .collect();

      format!("{{{}}}", pairs.join(", "))
    }

    yaml_serde::Value::Tagged(tagged) => yaml_value_to_flow_text(&tagged.value),
  }
}

pub fn yaml_value_to_block_text(value: &yaml_serde::Value, indent: usize) -> String {
  let prefix = " ".repeat(indent);

  match value {
    yaml_serde::Value::Sequence(sequence) => sequence
      .iter()
      .map(|item| match item {
        yaml_serde::Value::Mapping(_) => {
          let inner = yaml_value_to_block_text(item, indent + 2);
          format!("{}- {}", prefix, inner.trim_start())
        }

        _ => format!("{}- {}", prefix, yaml_value_to_flow_text(item)),
      })
      .collect::<Vec<_>>()
      .join("\n"),

    yaml_serde::Value::Mapping(mapping) => mapping
      .iter()
      .map(|(key, value)| {
        let key_string = match key {
          yaml_serde::Value::String(string) => string.clone(),
          _ => format!("{:?}", key),
        };
        match value {
          yaml_serde::Value::Sequence(_) | yaml_serde::Value::Mapping(_) => {
            let inner = yaml_value_to_block_text(value, indent + 2);

            format!("{}{}:\n{}", prefix, key_string, inner)
          }

          _ => format!("{}{}: {}", prefix, key_string, yaml_value_to_flow_text(value)),
        }
      })
      .collect::<Vec<_>>()
      .join("\n"),

    _ => format!("{}{}", prefix, yaml_value_to_flow_text(value)),
  }
}

fn format_yaml_scalar(value: &Value, quote_style: &QuoteStyle) -> String {
  match value {
    Value::Null => "null".to_string(),
    Value::Bool(boolean) => boolean.to_string(),
    Value::Number(number) => number.to_string(),
    Value::String(string) => match quote_style {
      QuoteStyle::Double => {
        let escaped = string.replace('\\', "\\\\").replace('"', "\\\"");

        format!("\"{}\"", escaped)
      }

      QuoteStyle::Single => {
        let escaped = string.replace('\'', "''");

        format!("'{}'", escaped)
      }

      QuoteStyle::Plain => {
        if crate::syntax::is_yaml_non_string(string) {
          format!("\"{}\"", string.replace('"', "\\\""))
        } else {
          string.clone()
        }
      }

      _ => {
        let escaped = string.replace('\\', "\\\\").replace('"', "\\\"");

        format!("\"{}\"", escaped)
      }
    },

    Value::Array(array) => {
      let items: Vec<String> = array.iter().map(|item| format_yaml_scalar(item, quote_style)).collect();

      format!("[{}]", items.join(", "))
    }

    Value::Object(_) => format!("{:?}", value),
  }
}