rootasrole 3.0.1

An alternative to sudo that uses Linux capabilities and Role based access control.
use std::{
    error::Error,
    fs::{self, File},
    io::{BufRead, BufReader, Write},
    path::Path,
};

use toml::Table;

fn package_version<P: AsRef<Path>>(path: P) -> Result<String, Box<dyn Error>> {
    let cargo_toml = fs::read_to_string(path)?;
    let value: Table = cargo_toml.parse::<Table>()?;
    Ok(value["package"]["version"]
        .as_str()
        .map(|s| s.to_string())
        .expect("Failed to get package version"))
}

fn write_version<'a>(f: &'a mut File, package_version: &'a str) -> Result<&'a str, Box<dyn Error>> {
    f.write_all(
        format!(
            "pub const PACKAGE_VERSION: &str = \"{}\";\n",
            package_version
        )
        .as_bytes(),
    )?;
    Ok(package_version)
}

fn set_cargo_version(package_version: &str, file: &str) -> Result<(), Box<dyn Error>> {
    let cargo_toml = File::open(std::path::Path::new(file)).expect("Cargo.toml not found");
    let reader = BufReader::new(cargo_toml);
    let lines = reader.lines().map(|l| l.unwrap()).collect::<Vec<String>>();
    let mut cargo_toml = File::create(std::path::Path::new(file)).expect("Cargo.toml not found");
    for line in lines {
        if line.starts_with("version") {
            writeln!(cargo_toml, "version = \"{}\"", package_version)?;
        } else {
            writeln!(cargo_toml, "{}", line)?;
        }
    }
    cargo_toml.sync_all()?;
    Ok(())
}

fn set_readme_version(package_version: &str, file: &str) -> Result<(), Box<dyn Error>> {
    let readme = File::open(std::path::Path::new(file)).expect("README.md not found");
    let reader = BufReader::new(readme);
    let lines = reader.lines().map(|l| l.unwrap()).collect::<Vec<String>>();
    let mut readme = File::create(std::path::Path::new(file)).expect("README.md not found");
    for line in lines {
        if line.starts_with("# RootAsRole (V") {
            let mut s = line.split("(V").next().unwrap().to_string();
            let end = line.split(')').nth(1).unwrap();
            s.push_str(&format!("(V{}){}", package_version, end));
            writeln!(readme, "{}", s)?;
        } else {
            writeln!(readme, "{}", line)?;
        }
    }
    readme.sync_all()?;
    Ok(())
}

fn main() {
    println!("cargo:rerun-if-changed=Cargo.toml");
    println!("cargo:rerun-if-changed=build.rs");

    let is_install = std::env::var("CARGO_INSTALL_ROOT").is_ok();
    if is_install {
        panic!("This crate is not meant to be installed with cargo install, please download .deb or .rpm and install it with your package manager.\nSee: https://lechatp.github.io/RootAsRole/faq.html");
    }
    if !std::path::Path::new("rar-common").exists() {
        return;
    }
    let package_version = package_version("Cargo.toml").expect("Failed to get package version");
    let dest_path = std::path::Path::new("rar-common")
        .join("src")
        .join("version.rs");
    if dest_path.exists()
        && fs::read_to_string(&dest_path)
            .unwrap()
            .ends_with(&format!("\"{}\";\n", package_version))
    {
        return;
    }
    let mut f = File::create(dest_path).unwrap();
    f.write_all(b"// This file is generated by build.rs\n")
        .unwrap();
    f.write_all(b"// Do not edit this file directly\n").unwrap();
    f.write_all(b"// Instead edit build.rs and run cargo build\n")
        .unwrap();
    write_version(&mut f, &package_version).expect("Failed to write version");

    if let Err(err) = set_cargo_version(&package_version, "rar-common/Cargo.toml") {
        eprintln!("cargo:warning={}", err);
    }

    if let Err(err) = set_cargo_version(&package_version, "xtask/Cargo.toml") {
        eprintln!("cargo:warning={}", err);
    }

    if let Err(err) = set_readme_version(&package_version, "README.md") {
        eprintln!("cargo:warning={}", err);
    }
}