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
)
)
}
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" => {
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)?;
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(())
}
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");
println!("Dependency '{dependency_name}' already exists in Cargo.toml",);
if doc["dependencies"].get(dependency_name).is_some() {
return Ok(());
}
doc["dependencies"][dependency_name] = value("*");
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."
)
});
add_dependency_to_package_xml("package.xml".to_string(), dependency)?;
println!("Attempting to add '{}' to Cargo.toml...", dependency);
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 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(())
}