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::{
    fs::{read_to_string, write},
    io::{Cursor, ErrorKind},
};
use toml_edit::{DocumentMut, value};

/// Parses environment arguments into CLI argument matches for the `ros-add` command.
///
/// This function handles two cases:
/// 1. When "ros-add" is present in the arguments (skips it for matching)
/// 2. When no special handling is needed (uses arguments as-is)
///
/// # Arguments
/// * `env_args` - A vector of strings representing the command line arguments
///
/// # Returns
/// `ArgMatches` object containing the parsed arguments
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 dependency to a ROS package.xml file while preserving formatting.
///
/// The dependency is added as a `<depend>` tag just before the closing `</package>` tag.
///
/// # Arguments
/// * `path` - Path to the package.xml file
/// * `dependency_name` - Name of the dependency to add
///
/// # Returns
/// `Result<(), ErrorKind>` where:
/// - `Ok(())` on success
/// - `Err(ErrorKind)` on failure with the specific error kind
///
/// # Errors
/// - `ErrorKind::NotFound` if the file cannot be read
/// - `ErrorKind::WriteZero` if there's an XML writing error
/// - `ErrorKind::Deadlock` if there's an XML parsing error
/// - `ErrorKind::InvalidData` if the package end tag is not found
pub 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(())
}

/// Adds a dependency to Cargo.toml with a wildcard version (*).
///
/// If the dependency already exists, this function does nothing.
///
/// # Arguments
/// * `path` - Path to the Cargo.toml file
/// * `dependency_name` - Name of the dependency to add
///
/// # Returns
/// `Result<(), ErrorKind>` where:
/// - `Ok(())` on success or if dependency already exists
/// - `Err(ErrorKind)` on file operation failures
///
/// # Errors
/// - Returns file operation errors if reading or writing fails
pub 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(())
}