smart-patcher 0.7.0

Patcher based on rules
Documentation
use anyhow::bail;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use uuid::Uuid;

use crate::utils::is_verbose;

fn run(cmd: impl AsRef<str>, files: &[&str], working_dir: &Path) -> anyhow::Result<()> {
  if !std::process::Command::new(cmd.as_ref())
    .args(files)
    .current_dir(working_dir)
    .status()?
    .success()
  {
    bail!("`{}` wasn't executed successfully!", cmd.as_ref())
  }
  Ok(())
}

#[derive(Deserialize, Serialize)]
struct FindByRequest {
  content: String,
  start_pos: Option<usize>,
  end_pos: Option<usize>,
}

#[derive(Deserialize, Serialize)]
struct FindByResponse {
  start_pos: Option<usize>,
  end_pos: Option<usize>,
  cursor_at_end: bool,
}

pub fn find_by(
  patch_dir: &Path,
  path: &Path,
  content: &str,
  start_pos: Option<usize>,
  end_pos: Option<usize>,
) -> anyhow::Result<(Option<usize>, Option<usize>, bool)> {
  let patch_path = patch_dir.to_path_buf().join(path);

  if !patch_path.is_file() {
    bail!("Shell script path ({:?}) has to be a file.", patch_path)
  }

  if is_verbose() { println!(">>> Find by sh: patch_path = {patch_path:?}"); }

  let req_filename = format!("find_by.req.{}.json", Uuid::new_v4().as_simple());
  let res_filename = format!("find_by.res.{}.json", Uuid::new_v4().as_simple());

  let req_path = patch_dir.join(&req_filename);
  let res_path = patch_dir.join(&res_filename);

  if is_verbose() {
    println!(">>> Find by sh: req_path = {req_path:?}");
    println!(">>> Find by sh: res_path = {res_path:?}");
  }

  let request = FindByRequest {
    content: content.to_string(),
    start_pos,
    end_pos,
  };
  let request = serde_json::to_string(&request)?;
  std::fs::write(&req_path, request)?;

  let script_filename = path.to_string_lossy().to_string();
  let result = run(
    script_filename,
    &[req_filename.as_str(), res_filename.as_str()],
    patch_dir,
  );

  if is_verbose() { println!(">>> Shell script completed (for `find_by` request): {path:?}"); }

  let response = std::fs::read_to_string(&res_path)?;
  let response = serde_json::from_str::<FindByResponse>(&response)?;

  let _ = std::fs::remove_file(&req_path);
  let _ = std::fs::remove_file(&res_path);

  result?;

  Ok((response.start_pos, response.end_pos, response.cursor_at_end))
}

#[derive(Deserialize, Serialize)]
struct ReplaceByRequest {
  content: String,
}

#[derive(Deserialize, Serialize)]
struct ReplaceByResponse {
  content: String,
}

pub fn replace_by(patch_dir: &Path, path: &Path, content: &str) -> anyhow::Result<String> {
  let patch_path = patch_dir.to_path_buf().join(path);

  if !patch_path.is_file() {
    bail!("Shell script path ({:?}) has to be a file.", patch_path)
  }

  if is_verbose() { println!(">>> Replace by sh: patch_path = {patch_path:?}"); }

  let req_filename = format!("replace.req.{}.json", Uuid::new_v4().as_simple());
  let res_filename = format!("replace.res.{}.json", Uuid::new_v4().as_simple());

  let req_path = patch_dir.join(&req_filename);
  let res_path = patch_dir.join(&res_filename);

  if is_verbose() {
    println!(">>> Replace by sh: req_path = {req_path:?}");
    println!(">>> Replace by sh: res_path = {res_path:?}");
  }

  let request = ReplaceByRequest {
    content: content.to_string(),
  };
  let request = serde_json::to_string(&request)?;
  std::fs::write(&req_path, request)?;

  let script_filename = path.to_string_lossy().to_string();
  let result = run(
    script_filename,
    &[req_filename.as_str(), res_filename.as_str()],
    patch_dir,
  );

  if is_verbose() { println!(">>> Shell script completed (for `replace_by` request): {path:?}"); }

  let response = std::fs::read_to_string(&res_path)?;
  let response = serde_json::from_str::<ReplaceByResponse>(&response)?;

  let _ = std::fs::remove_file(&req_path);
  let _ = std::fs::remove_file(&res_path);

  result?;

  Ok(response.content)
}

#[derive(Deserialize, Serialize)]
struct DecodeByRequest {
  encoded_file: PathBuf,
}

#[derive(Deserialize, Serialize)]
struct DecodeByResponse {
  content: String,
}

pub fn decode_by(patch_dir: &Path, path: &Path, encoded_filepath: PathBuf) -> anyhow::Result<String> {
  let patch_path = patch_dir.to_path_buf().join(path);

  if !patch_path.is_file() {
    bail!("Shell script path ({:?}) has to be a file.", patch_path)
  }

  if is_verbose() { println!(">>> Decode by sh: patch_path = {patch_path:?}"); }

  let req_filename = format!("decode.req.{}.json", Uuid::new_v4().as_simple());
  let res_filename = format!("decode.res.{}.json", Uuid::new_v4().as_simple());

  let req_path = patch_dir.join(&req_filename);
  let res_path = patch_dir.join(&res_filename);

  if is_verbose() {
    println!(">>> Decode by sh: req_path = {req_path:?}");
    println!(">>> Decode by sh: res_path = {res_path:?}");
  }

  let request = DecodeByRequest {
    encoded_file: encoded_filepath,
  };
  let request = serde_json::to_string(&request)?;
  std::fs::write(&req_path, request)?;

  let script_filename = path.to_string_lossy().to_string();
  let result = run(
    script_filename,
    &[req_filename.as_str(), res_filename.as_str()],
    patch_dir,
  );

  if is_verbose() { println!(">>> Shell script completed (for `decode_by` request): {path:?}"); }

  let response = std::fs::read_to_string(&res_path)?;
  let response = serde_json::from_str::<DecodeByResponse>(&response)?;

  let _ = std::fs::remove_file(&req_path);
  let _ = std::fs::remove_file(&res_path);

  result?;

  Ok(response.content)
}

#[derive(Deserialize, Serialize)]
struct EncodeByRequest {
  write_to_file: PathBuf,
  content: String,
}

pub fn encode_by(patch_dir: &Path, path: &Path, content: &str, write_to_filepath: PathBuf) -> anyhow::Result<()> {
  let patch_path = patch_dir.to_path_buf().join(path);

  if !patch_path.is_file() {
    bail!("Shell script path ({:?}) has to be a file.", patch_path)
  }

  if is_verbose() { println!(">>> Encode by sh: patch_path = {patch_path:?}"); }

  let req_filename = format!("encode.req.{}.json", Uuid::new_v4().as_simple());
  let req_path = patch_dir.join(&req_filename);

  if is_verbose() {
    println!(">>> Encode by sh: req_path = {req_path:?}");
  }

  let request = EncodeByRequest {
    write_to_file: write_to_filepath,
    content: content.to_string(),
  };
  let request = serde_json::to_string(&request)?;
  std::fs::write(&req_path, request)?;

  let script_filename = path.to_string_lossy().to_string();
  let result = run(script_filename, &[req_filename.as_str()], patch_dir);

  if is_verbose() { println!(">>> Shell script completed (for `encode_by` request): {path:?}"); }

  let _ = std::fs::remove_file(&req_path);
  result?;

  Ok(())
}