#![warn(missing_docs, broken_intra_doc_links)]
use anyhow::Context;
use cargo_toml::Manifest;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
pub fn dot_crate(
dot_crate: impl AsRef<Path>,
old_name: Option<&str>,
new_name: &str,
) -> anyhow::Result<()> {
let dot_crate = dot_crate.as_ref();
let old_fn = dot_crate
.file_name()
.ok_or_else(|| anyhow::anyhow!(".crate file path '{}' is not a file", dot_crate.display()))?
.to_str()
.ok_or_else(|| {
anyhow::anyhow!(
".crate file path '{}' is not valid utf-8",
dot_crate.display()
)
})?;
let mut prefix = None;
if let Some(dot) = old_fn.find('.') {
if let Some(dash) = old_fn[..dot].rfind('-') {
let name = &old_fn[..dash];
let major = &old_fn[(dash + 1)..dot];
if !name.is_empty() && !major.is_empty() && major.chars().all(|c| c.is_ascii_digit()) {
prefix = Some(name);
}
}
}
if old_name.is_some() && prefix != old_name {
anyhow::bail!(
".crate file '{}' does not match given old name '{}'",
dot_crate.display(),
old_name
.as_ref()
.expect("check for is_some in if conditional"),
);
}
let old_name = old_name
.or(prefix)
.ok_or_else(|| anyhow::anyhow!("failed to infer current crate name"))?;
let repackaged_fn = old_fn.replace(old_name, new_name);
let repackaged_path = dot_crate.with_file_name(repackaged_fn);
let repackaged = std::fs::File::create(&repackaged_path)?;
let dot_crate_path = dot_crate;
let dot_crate = std::fs::File::open(&dot_crate_path)?;
let dot_crate = flate2::read::GzDecoder::new(dot_crate);
let mut dot_crate = tar::Archive::new(dot_crate);
let repackaged = flate2::GzBuilder::new().write(repackaged, flate2::Compression::best());
let mut repackaged = tar::Builder::new(repackaged);
let from = format!(" {}::", old_name.replace('-', "_"));
let to = format!(" {}::", new_name.replace('-', "_"));
let old_base_dir = {
let mut d = PathBuf::from(old_fn);
d.set_extension("");
d
};
let new_base_dir = {
let mut d = PathBuf::from(old_fn.replace(old_name, new_name));
d.set_extension("");
d
};
let mut got_cargo_toml = false;
let mut file_bytes = String::new();
for file in dot_crate
.entries()
.context("walk entries from .crate file")?
{
let mut file = file.context("walk entry from .crate file")?;
let mut header = file.header().clone();
let path = file.path().context("get .crate file entry path")?;
let sub_path = path.strip_prefix(&old_base_dir).map_err(|_| {
anyhow::anyhow!(
".crate contained entry not under old crate subdir: {}",
path.display()
)
})?;
let path = new_base_dir.join(sub_path);
if path.file_name() == Some(std::ffi::OsStr::new("Cargo.toml")) {
let mut toml_bytes = Vec::with_capacity(file.size() as usize);
file.read_to_end(&mut toml_bytes)
.context("read Cargo.toml from .crate file")?;
let mut manifest =
Manifest::from_slice(&toml_bytes).context("parse Cargo.toml from .crate file")?;
if manifest.workspace.is_some() {
anyhow::bail!(".crate file is a workspace, so is not packaged");
}
let p = manifest.package.as_mut().ok_or_else(|| {
anyhow::anyhow!("Cargo.toml in .crate file does not contain a package")
})?;
if &p.name != old_name {
anyhow::bail!(
"crate name in .crate ('{}') file did not match given name ('{}')",
p.name,
old_name
);
}
p.name = new_name.to_string();
let manifest =
toml::Value::try_from(&manifest).context("serialize modified Cargo.toml")?;
let bytes = toml::to_vec(&manifest).context("serialize modified Cargo.toml")?;
let mut bytes = &bytes[..]; header.set_size(bytes.len() as u64);
header.set_cksum();
repackaged
.append_data(&mut header, path, &mut bytes)
.context("append modified Cargo.toml to new .crate file")?;
got_cargo_toml = true;
} else if !path.starts_with("src") && path.extension().map(|e| e == "rs").unwrap_or(false) {
file_bytes.clear();
file.read_to_string(&mut file_bytes)
.context("read .rs file for in-place modification")?;
let file_bytes = if file_bytes.contains(&from) {
std::borrow::Cow::Owned(file_bytes.replace(&from, &to))
} else {
std::borrow::Cow::Borrowed(&file_bytes)
};
header.set_size(file_bytes.bytes().len() as u64);
repackaged.append_data(&mut header, path, &mut file_bytes.as_bytes())?;
} else {
repackaged
.append_data(&mut header, path, file)
.context("append unmodified file to new .crate file")?;
}
}
if !got_cargo_toml {
let _ = std::fs::remove_file(repackaged_path);
anyhow::bail!(
".crate file {} did not contain a Cargo.toml file",
dot_crate_path.display()
);
}
Ok(())
}