yerba 0.5.1

YAML Editing and Refactoring with Better Accuracy
use std::sync::LazyLock;

use indoc::indoc;

use super::colorize_examples;
use super::{output, parse_file, resolve_files, run_op};

static EXAMPLES: LazyLock<String> = LazyLock::new(|| {
  colorize_examples(indoc! {r#"
    yerba insert config.yml "database.ssl" true
    yerba insert config.yml "database.ssl" true --after "host"
    yerba insert config.yml "database.ssl" true --before "port"
    yerba insert config.yml "tags" "yaml"
    yerba insert config.yml "tags" "yaml" --at 0
    yerba insert config.yml "tags" "yaml" --after "ruby"
    yerba insert speakers.yml "" "name: Bob" --after ".name == Alice"
    yerba insert videos.yml "[0].speakers" "Diana" --before ".name == Charlie"
    yerba insert videos.yml "" --from "new_talk.yml" --after ".id == first-talk"
  "#})
});

#[derive(clap::Args)]
#[command(
  about = "Insert a new key into a map or item into a sequence",
  arg_required_else_help = true,
  after_help = EXAMPLES.as_str()
)]
pub struct Args {
  file: String,
  selector: String,
  value: Option<String>,
  #[arg(long, help = "Read value from a file (use - for stdin)")]
  from: Option<String>,
  #[arg(long, help = "Insert before this value or condition (e.g. \".name == Alice\")")]
  before: Option<String>,
  #[arg(long, help = "Insert after this value or condition (e.g. \".name == Alice\")")]
  after: Option<String>,
  #[arg(long)]
  at: Option<usize>,
  #[arg(long)]
  dry_run: bool,
}

impl Args {
  pub fn run(self) {
    let resolved_value = if let Some(from_path) = self.from {
      if from_path == "-" {
        use std::io::Read;
        let mut buffer = String::new();

        std::io::stdin().read_to_string(&mut buffer).unwrap_or_else(|error| {
          use super::color::*;
          eprintln!("{RED}Error:{RESET} reading stdin: {}", error);

          std::process::exit(1);
        });

        buffer.trim().to_string()
      } else {
        std::fs::read_to_string(&from_path)
          .unwrap_or_else(|error| {
            use super::color::*;
            eprintln!("{RED}Error:{RESET} reading {}: {}", from_path, error);
            std::process::exit(1);
          })
          .trim()
          .to_string()
      }
    } else if let Some(val) = self.value {
      val
    } else {
      use super::color::*;
      eprintln!("{RED}Error:{RESET} either a value argument or --from is required");

      std::process::exit(1);
    };

    let parent_path = self.selector.rsplit_once('.').map(|(parent, _)| parent).unwrap_or("");

    let position = if let Some(index) = self.at {
      yerba::InsertPosition::At(index)
    } else if let Some(target) = self.before {
      if target.starts_with('.') {
        yerba::InsertPosition::BeforeCondition(target)
      } else {
        yerba::InsertPosition::Before(target)
      }
    } else if let Some(target) = self.after {
      if target.starts_with('.') {
        yerba::InsertPosition::AfterCondition(target)
      } else {
        yerba::InsertPosition::After(target)
      }
    } else {
      yerba::Yerbafile::find()
        .and_then(|yerbafile_path| yerba::Yerbafile::load(&yerbafile_path).ok())
        .and_then(|yerbafile| yerbafile.sort_order_for(&self.file, parent_path))
        .map(yerba::InsertPosition::FromSortOrder)
        .unwrap_or(yerba::InsertPosition::Last)
    };

    for resolved_file in resolve_files(&self.file) {
      let mut document = parse_file(&resolved_file);
      let result = document.insert_into(&self.selector, &resolved_value, position.clone());

      run_op(&self.file, &document, result);
      output(&resolved_file, &document, self.dry_run);
    }
  }
}