ros_add 0.1.1

The Purpose of the Package is to provide the `cargo ros_add` command to add dependencies to `Cargo.toml` and the `package.xml`
Documentation
use case_clause::case;
use clap::{Arg, ArgMatches, Command as ClapCommand};
use quick_xml::{
    Reader, Writer,
    events::{BytesEnd, BytesStart, BytesText, Event},
};
use std::{
    env,
    fs::{read_to_string, write},
    io::{Cursor, ErrorKind},
    path::Path,
    process::Command,
};
use toml_edit::{DocumentMut, value};

pub fn env_to_matches(env_args: Vec<String>) -> ArgMatches {
    ClapCommand::new("ros_add")
        .version("0.1.0")
        .author("GueLaKais <koroyeldiores@gmail.com>")
        .about("adds dependencies to ros rust packages and package.xml")
        .arg(
            Arg::new("dependency")
                .required(true)
                .help("Name of the dependency to add")
                .index(1),
        )
        .get_matches_from(
            case!(
                env_args.iter().any(|arg| arg == "ros-add") => env_args.iter().take(1).chain(env_args.iter().skip(2)).cloned().collect::<Vec<String>>(),
                true => env_args
            )
        )
}

/// Adds a <depend> tag to the package.xml file in the current directory while preserving formatting.
fn add_dependency_to_package_xml(path: String, dependency_name: &str) -> Result<(), ErrorKind> {
    let mut reader = read_to_string(path.as_str())
        .map(|content| Reader::from_str(Box::leak(content.into_boxed_str())))
        .map_err(|_| ErrorKind::NotFound)?;
    reader.config_mut().trim_text(true);

    let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2);
    let mut buf = Vec::new();
    let mut found_package_end = false;

    loop {
        match reader.read_event_into(&mut buf) {
            Ok(Event::End(ref e)) if e.name().as_ref() == b"package" => {
                // Write the new dependency before the package end tag
                let start = BytesStart::new("depend");
                writer
                    .write_event(Event::Start(start))
                    .map_err(|_| ErrorKind::WriteZero)?;
                writer
                    .write_event(Event::Text(BytesText::new(dependency_name)))
                    .map_err(|_| ErrorKind::WriteZero)?;
                writer
                    .write_event(Event::End(BytesEnd::new("depend")))
                    .map_err(|_| ErrorKind::WriteZero)?;

                // Then write the package end tag
                writer
                    .write_event(Event::End(e.clone()))
                    .map_err(|_| ErrorKind::WriteZero)?;
                found_package_end = true;
            }
            Ok(Event::Eof) => break,
            Ok(e) => writer.write_event(e).map_err(|_| ErrorKind::WriteZero)?,
            Err(_) => return Err(ErrorKind::Deadlock),
        }
        buf.clear();
    }

    if !found_package_end {
        return Err(ErrorKind::InvalidData);
    }

    write(path.as_str(), writer.into_inner().into_inner()).map_err(|e| e.kind())?;

    println!("Successfully added dependency '{dependency_name}' to {path}");
    Ok(())
}

/// Properly adds a dependency to Cargo.toml with a wildcard version
fn add_dependency_to_cargo_toml(path: String, dependency_name: &str) -> Result<(), ErrorKind> {
    let mut doc = read_to_string(path.as_str())
        .map_err(|e| e.kind())?
        .parse::<DocumentMut>()
        .expect("invalid TOML");

    // Check if the dependency already exists
    println!("Dependency '{dependency_name}' already exists in Cargo.toml",);
    if doc["dependencies"].get(dependency_name).is_some() {
        return Ok(());
    }

    // Add the dependency with wildcard version
    doc["dependencies"][dependency_name] = value("*");

    // Write back to file
    write(path.as_str(), doc.to_string()).map_err(|e| e.kind())?;
    Ok(())
}

fn main() -> Result<(), ErrorKind> {
    let matches: ArgMatches = env_to_matches(env::args().collect());

    let dependency = matches.get_one::<String>("dependency").unwrap_or_else(|| {
        panic!(
            "Argument 'dependency' is required but missing. Check `env_to_matches` implementation."
        )
    });

    // First add to package.xml
    add_dependency_to_package_xml("package.xml".to_string(), dependency)?;

    println!("Attempting to add '{}' to Cargo.toml...", dependency);

    // First try with cargo add
    if !Command::new("cargo")
        .arg("add")
        .arg(format!("{}@*", dependency))
        .status()
        .map_err(|e| {
            eprintln!("Warning: 'cargo add' failed: {}", e);
            e.kind()
        })?
        .success()
    {
        eprintln!("Warning: 'cargo add' command failed");
    }

    // If Cargo.toml exists, try to add the dependency properly
    if Path::new("Cargo.toml").exists() {
        match add_dependency_to_cargo_toml("Cargo.toml".to_string(), dependency) {
            Ok(_) => println!("Successfully added '{dependency}' to Cargo.toml"),
            Err(e) => eprintln!("Warning: Failed to add dependency to Cargo.toml: {e}"),
        }
    } else {
        eprintln!("Warning: Cargo.toml not found - dependency not added");
    }

    Ok(())
}