cargo-v 1.0.1

Update the version of your package easily
Documentation
use std::error::Error;

pub fn get_version_from_cargo_toml(
  cargo_toml: &str,
) -> Result<String, Box<dyn Error>> {
  get_prop_from_cargo_toml(cargo_toml, "version")
}

pub fn get_version_from_cargo_lock(
  cargo_lock: &str,
  cargo_toml: &str,
) -> Result<String, Box<dyn Error>> {
  let mut version: Option<String> = None;
  let lines: Vec<&str> = cargo_lock.lines().collect();
  let mut should_copy = false;
  let project_name = get_name_from_cargo_toml(cargo_toml)?;

  for line in lines {
    if line.starts_with("name") {
      should_copy = line.starts_with(&format!("name = \"{}\"", project_name));
    }

    if should_copy && line.starts_with("version") {
      version = Some(get_value_from_toml_line(line, "version"));
    }
  }

  match version {
    Some(v) => Ok(v),
    None => Err("Cargo.lock does not have a `version` entry")?,
  }
}

pub fn set_version_in_cargo_toml(cargo_toml: &str, version: &str) -> String {
  let lines: Vec<&str> = cargo_toml.lines().collect();
  let mut new_cargo_toml: Vec<String> = Vec::new();
  let mut should_write = false;

  for line in lines {
    let mut new_line = line.to_owned();
    if line.starts_with('[') {
      should_write = line.starts_with("[package]");
    }

    if should_write && line.starts_with("version") {
      new_line = format!("version = \"{}\"", version);
    }

    new_cargo_toml.push(new_line);
  }

  new_cargo_toml.join("\n")
}

pub fn set_version_in_cargo_lock(
  cargo_lock: &str,
  project_name: &str,
  version: &str,
) -> String {
  let lines: Vec<&str> = cargo_lock.lines().collect();
  let mut new_cargo_lock: Vec<String> = Vec::new();
  let mut should_write = false;

  for line in lines {
    let mut new_line = line.to_owned();
    if line.starts_with("name") {
      should_write = line.starts_with(&format!("name = \"{}\"", project_name));
    }

    if should_write && line.starts_with("version") {
      new_line = format!("version = \"{}\"", version);
    }

    new_cargo_lock.push(new_line);
  }

  new_cargo_lock.join("\n")
}

pub fn get_name_from_cargo_toml(
  cargo_toml: &str,
) -> Result<String, Box<dyn Error>> {
  get_prop_from_cargo_toml(cargo_toml, "name")
}

fn get_prop_from_cargo_toml(
  cargo_toml: &str,
  prop: &str,
) -> Result<String, Box<dyn Error>> {
  let mut property: Option<String> = None;
  let lines: Vec<&str> = cargo_toml.lines().collect();
  let mut should_copy = false;

  for line in lines {
    if line.starts_with('[') {
      should_copy = line.starts_with("[package]")
    }

    if should_copy && line.starts_with(prop) {
      property = Some(get_value_from_toml_line(line, prop));
    }
  }

  match property {
    Some(v) => Ok(v),
    None => Err(format!(
      "Cargo.toml does not have a `{prop}` entry inside [package]"
    ))?,
  }
}

fn get_value_from_toml_line(line: &str, prop: &str) -> String {
  line
    .replace(prop, "")
    .replace('=', "")
    .replace('"', "")
    .trim()
    .to_string()
}

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

  fn get_cargo_toml(version: &str) -> String {
    format!("\
[dependencies]
tokio = {{ version = \"1.1.1\" }}

[package]
name = \"cargo-v\"
version = \"{}\"
edition = \"2021\"
description = \"Update the version of your package easily\"
license = \"MIT\"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.dev]
other = {{ version = \"1.1.8\" }}
      ",
    version)
  }

  fn get_cargo_lock(version: &str) -> String {
    format!(
      "
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = \"tokio\"
version = \"0.1.8\"

[[package]]
name = \"cargo-v\"
version = \"{}\"

[[package]]
name = \"regex\"
version = \"0.1.8\"
      ",
      version
    )
  }

  #[test]
  fn should_get_version_from_cargo_toml() {
    let cargo_toml = &get_cargo_toml("0.1.6");
    let actual = get_version_from_cargo_toml(cargo_toml).unwrap();
    let expected = "0.1.6";
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_get_name_from_cargo_toml() {
    let cargo_toml = &get_cargo_toml("0.1.6");
    let actual = get_name_from_cargo_toml(cargo_toml).unwrap();
    let expected = "cargo-v";
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_get_property_edition_from_cargo_toml() {
    let cargo_toml = &get_cargo_toml("0.1.6");
    let actual = get_prop_from_cargo_toml(&cargo_toml, "edition").unwrap();
    let expected = "2021";
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_get_version_from_cargo_lock() {
    let cargo_toml = &get_cargo_toml("0.1.6");
    let cargo_lock = &get_cargo_lock("0.1.6");
    let actual = get_version_from_cargo_lock(cargo_lock, cargo_toml).unwrap();
    let expected = "0.1.6";
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_get_value_from_line_in_cargo_toml() {
    let line = "version = \"1.1.1\"";
    let actual = get_value_from_toml_line(line, "version");
    let expected = "1.1.1";
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_write_new_version_in_cargo_toml() {
    let cargo_toml = get_cargo_toml("0.1.6");
    let actual = set_version_in_cargo_toml(&cargo_toml, "0.1.7");
    let expected = get_cargo_toml("0.1.7");
    assert_eq!(actual, expected);
  }

  #[test]
  fn should_write_new_version_in_cargo_lock() {
    let cargo_toml = &get_cargo_toml("0.1.6");
    let cargo_lock = get_cargo_lock("0.1.6");
    let project_name = &get_name_from_cargo_toml(cargo_toml).unwrap();
    let actual = set_version_in_cargo_lock(&cargo_lock, project_name, "0.1.7");
    let expected = get_cargo_lock("0.1.7");
    assert_eq!(actual, expected);
  }
}