use case_clause::case;
use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
use quick_xml::{
NsReader, Writer,
events::{BytesText, Event},
};
use std::{
fs::{File, read_to_string, write},
io::{BufReader, Cursor, ErrorKind},
};
use toml_edit::{DocumentMut, value};
#[derive(Clone, Copy, Debug)]
pub enum DependencyType {
BuildDepend,
BuildExportDepend,
BuildtoolDepend,
BuildtoolExportDepend,
ExecDepend,
Depend,
DocDepend,
TestDepend,
Conflict,
Replace,
}
impl DependencyType {
pub fn new(flag: ArgMatches) -> Self {
case! (
flag.get_flag("build")=> DependencyType::BuildDepend,
flag.get_flag("build-export") => DependencyType::BuildExportDepend,
flag.get_flag("buildtool") => DependencyType::BuildtoolDepend,
flag.get_flag("buildtool-export") => DependencyType::BuildtoolExportDepend,
flag.get_flag("exec") => DependencyType::TestDepend,
flag.get_flag("doc") => DependencyType::DocDepend,
flag.get_flag("test") => DependencyType::TestDepend,
flag.get_flag("conflict") => DependencyType::Conflict,
flag.get_flag("replace") => DependencyType::Replace,
true => DependencyType::Depend,
)
}
pub fn cargo_toml_type(&self) -> &str {
match self {
DependencyType::BuildDepend => "build-dependencies",
_ => "dependencies",
}
}
pub fn package_xml_type(&self) -> &str {
match self {
DependencyType::Replace => "replace",
DependencyType::Conflict => "conflict",
DependencyType::TestDepend => "test-depend",
DependencyType::DocDepend => "doc-depend",
DependencyType::BuildtoolDepend => "buildtool-depend",
DependencyType::ExecDepend => "exec-depend",
DependencyType::BuildtoolExportDepend => "build_export_depend",
DependencyType::BuildDepend => "build_depend",
DependencyType::BuildExportDepend => "build_export_depend",
DependencyType::Depend => "depend",
}
}
}
pub fn env_to_matches(env_args: Vec<String>) -> ArgMatches {
ClapCommand::new("ros_add")
.version("0.1.0")
.author("GueLaKais <koroyeldiores@gmail.com>")
.about("Add dependencies to a Cargo.toml and package.xml manifest file")
.arg(
Arg::new("dependency").value_name("DEP_ID")
.help(["Reference to a package to add as a dependency","You can reference a package by:","- `<name>`, like `cargo ros-add serde` (latest version will be used)","- `<name>@<version-req>`, like `cargo add serde@1` or `cargo add serde@=1.0.38`"].join("\n")).required(true)
)
.arg(Arg::new("color").help("Coloring\n \n").long("color").value_name("WHEN").value_parser(["auto", "always", "never"]))
.arg(Arg::new("no_cargo_toml").action(ArgAction::SetTrue).help("Dependency will only be added to package.xml file.").long("no-cargo-toml").required(false))
.arg(Arg::new("no_package_xml").action(ArgAction::SetTrue).help("Dependency will only be added to the Cargo.toml file.").long("no-package-xml").required(false))
.arg(Arg::new("build").action(ArgAction::SetTrue).help(["add as build dependency","will appear in Cargo.toml as [build-dependencies] and in package.xml as build_depend element","Build-dependencies are only dependencies available for use by build scripts (`build.rs` files)."].join("\n")).long("build"))
.arg(Arg::new("build-export").action(ArgAction::SetTrue).help("package.xml only. What even is that?"))
.arg(Arg::new("buildtool").action(ArgAction::SetTrue).help("package.xml only. These are for packages that provide the build infrastructure"))
.arg(Arg::new("buildtool-export").action(ArgAction::SetTrue).help("package.xml only. What even is that?"))
.arg(Arg::new("exec").action(ArgAction::SetTrue).help("package.xml only. Because Rust has no runtime applicable dependencies, this won't work"))
.arg(Arg::new("doc").action(ArgAction::SetTrue).help("package.xml only. What even is that?"))
.arg(Arg::new("test").action(ArgAction::SetTrue).help("only in package.xml a different dependency type. Will appear in the Cargo.toml as standard dependency"))
.arg(Arg::new("conflict").action(ArgAction::SetTrue).help("package.xml only. What even is that?"))
.arg(Arg::new("replace").action(ArgAction::SetTrue).help("package.xml only. What even is that?"))
.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
)
)
}
pub struct PathDoc {
path: String,
doc: DocumentMut,
dependency_type: DependencyType,
}
impl PathDoc {
pub fn new(path: String, dependency_type: DependencyType) -> Result<PathDoc, ErrorKind> {
Ok(PathDoc {
path: path.clone(),
doc: read_to_string(path.as_str())
.map_err(|e| e.kind())?
.parse::<DocumentMut>()
.map_err(|_| ErrorKind::InvalidData)?,
dependency_type,
})
}
pub fn add_dependency_to_cargo_toml(mut self, dependency_name: &str) -> Result<(), ErrorKind> {
if self
.doc
.get(self.dependency_type.cargo_toml_type())
.is_none()
{
self.doc[self.dependency_type.cargo_toml_type()] = toml_edit::table();
}
match self.doc[self.dependency_type.cargo_toml_type()].get(dependency_name) {
Some(_) => {
eprintln!("{dependency_name} already added to dependencies");
Err(ErrorKind::AlreadyExists)
}
None => {
self.doc[self.dependency_type.cargo_toml_type()][dependency_name] = value("*");
write(self.path.as_str(), self.doc.to_string()).map_err(|e| e.kind())?;
println!("Dependency {dependency_name} added to Cargo.toml");
Ok(())
}
}
}
}
pub struct XMLHelper {
pub path: String,
pub reader: NsReader<BufReader<File>>,
pub writer: Writer<Cursor<Vec<u8>>>,
pub buf: Vec<u8>,
dependency_type: DependencyType,
}
impl XMLHelper {
pub fn new(path: String, dependency_type: DependencyType) -> Result<XMLHelper, ErrorKind> {
Ok(XMLHelper {
path: path.clone(),
reader: NsReader::from_file(&path).map_err(|_| ErrorKind::NotFound)?,
writer: Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2),
buf: Vec::new(),
dependency_type,
})
}
pub fn add_dependency_to_package_xml(mut self, dependency_name: &str) -> Result<(), ErrorKind> {
self.reader.config_mut().trim_text(true);
loop {
self.buf.clear();
match self.reader.read_event_into(&mut self.buf) {
Ok(Event::Start(e))
if e.name().as_ref() == self.dependency_type.package_xml_type().as_bytes() =>
{
self.writer
.write_event(Event::Start(e.clone()))
.map_err(|_| ErrorKind::WriteZero)?;
match self.reader.read_event_into(&mut self.buf) {
Ok(Event::Text(text)) => {
if self
.reader
.decoder()
.decode(text.as_ref())
.unwrap_or_default()
== dependency_name
{
eprintln!(
"Dependency '{dependency_name}' already exists in package.xml"
);
return Err(ErrorKind::AlreadyExists);
}
self.writer
.write_event(Event::Text(text))
.map_err(|_| ErrorKind::WriteZero)?;
}
_ => {
println!("Everything allright")
}
}
}
Ok(Event::Start(e)) if e.name().as_ref() == b"export" => {
self.writer
.create_element(self.dependency_type.package_xml_type())
.write_text_content(BytesText::new(dependency_name))
.map_err(|_| ErrorKind::OutOfMemory)?;
self.writer
.write_event(Event::Start(e))
.map_err(|_| ErrorKind::WriteZero)?;
}
Ok(Event::Eof) => break,
Ok(e) => self
.writer
.write_event(e)
.map_err(|_| ErrorKind::WriteZero)?,
Err(_) => return Err(ErrorKind::InvalidData),
}
}
write(&self.path, self.writer.into_inner().into_inner()).map_err(|e| e.kind())?;
Ok(())
}
}