#![warn(clippy::all)]
extern crate cargo;
use cargo::core::compiler::RustcTargetData;
use cargo::core::resolver::{ForceAllTargets, HasDevUnits};
use cargo::core::Workspace;
use cargo::core::{resolver::CliFeatures, shell::Shell};
use cargo::ops;
use cargo::util::{CargoResult, CliError, context::GlobalContext as GCTXT};
use cargo::CliResult;
extern crate cargo_author;
use cargo_author::Author;
extern crate ripemd;
use ripemd::{Digest, Ripemd160};
extern crate serde;
use serde::{Deserialize, Serialize};
extern crate clap;
use clap::Parser;
use std::{
collections::{BTreeMap, HashSet},
env,
path::Path,
};
#[derive(Clone, Debug, Parser)]
#[command(version, about="List all authors of all dependencies of the current crate.", long_about=None)]
struct Flags {
#[arg(short, long, action=clap::ArgAction::SetTrue)]
json: bool,
#[arg(short='a', long, action=clap::ArgAction::SetTrue)]
hide_authors: bool,
#[arg(short='e', long, action=clap::ArgAction::SetTrue)]
hide_emails: bool,
#[arg(short='c', long, action=clap::ArgAction::SetTrue)]
hide_crates: bool,
#[arg(short='i', long, action=clap::ArgAction::SetTrue)]
ignore_self: bool,
#[arg(long, action=clap::ArgAction::SetTrue)]
by_crate: bool,
#[arg(short, long, value_name="path")]
path: Option<String>,
}
#[derive(Serialize, Deserialize)]
struct AuthorsResult {
entries: BTreeMap<String, HashSet<String>>,
}
impl AuthorsResult {
fn new(v: BTreeMap<String, HashSet<String>>) -> Self {
AuthorsResult { entries: v }
}
}
#[derive(Clone)]
struct DependencyAccumulator<'a> {
gctxt: &'a GCTXT,
flags: Flags,
}
type Aggregate = CargoResult<BTreeMap<String, HashSet<String>>>;
impl<'a> DependencyAccumulator<'a> {
fn new(c: &'a GCTXT, flags: Flags) -> Self {
DependencyAccumulator { gctxt: c, flags }
}
fn accumulate(&self) -> Aggregate {
let path = self
.flags
.path
.clone()
.unwrap_or_else(|| String::from("."));
let local_root = Path::new(path.as_str()).canonicalize()?;
let local_root = local_root.as_path();
let ws_path = local_root.join("Cargo.toml");
let ws = Workspace::new(&ws_path, self.gctxt)?;
let self_pkg = ws.current()?.name().as_str();
let mut target_data = RustcTargetData::new(&ws, &[])?;
let deps = ops::resolve_ws_with_opts(
&ws,
&mut target_data,
&[],
&CliFeatures::new_all(true),
&[],
HasDevUnits::Yes,
ForceAllTargets::Yes,
false )?;
let package_set = deps.pkg_set;
let mut result: BTreeMap<String, HashSet<String>> = BTreeMap::new();
for pkg in package_set.packages() {
let package = pkg.clone();
let name = if self.flags.hide_crates {
format!("{:x}", Ripemd160::digest(package.name().as_bytes()))
} else {
package.name().to_string()
};
if name == self_pkg && self.flags.ignore_self {
continue;
}
let authors = package.authors().iter().map(|e| {
if self.flags.hide_authors {
format!("{:x}", Ripemd160::digest(e.as_bytes()))
} else if self.flags.hide_emails {
let author = Author::new(e);
format!(
"{}{}{}",
author.name.as_deref().unwrap_or_default(),
if author.name.is_some() && author.email.is_some() {
" "
} else {
""
},
author
.email
.as_ref()
.map(|s| format!("<{:x}>", Ripemd160::digest(s.as_bytes())))
.unwrap_or_default()
)
} else {
e.clone()
}
});
for auth in authors {
let crates = result.entry(auth).or_default();
crates.insert(name.to_string());
}
}
if self.flags.by_crate {
let mut result_: BTreeMap<String, HashSet<String>> = BTreeMap::new();
for (k, vs) in result.iter() {
for v in vs.iter().cloned() {
let authors = result_.entry(v).or_default();
authors.insert(k.clone());
}
}
result = result_;
}
Ok(result)
}
}
fn real_main(flags: Flags, gctxt: &GCTXT) -> CliResult {
let aggregate = match DependencyAccumulator::new(gctxt, flags.clone()).accumulate() {
Err(ref e) => {
println!("error: {}", e);
for e in e.chain() {
println!("caused by: {}", e);
}
::std::process::exit(1);
}
Ok(agg) => agg,
};
let max_author_len = aggregate
.keys()
.map(|e| e.len())
.max()
.unwrap_or_default();
if flags.json {
let ar = AuthorsResult::new(aggregate);
let ar_json = serde_json::to_string(&ar).unwrap();
println!("{}", ar_json);
} else {
println!("Authors and their respective crates for this crate:\n");
for (author, crates) in aggregate {
let crates = crates.into_iter().collect::<Vec<String>>();
let crates = crates[..].join(", ");
println!("{:<N$}: {}", author, crates, N = max_author_len);
}
}
Ok(())
}
fn main() {
let gctxt = match GCTXT::default() {
Ok(cfg) => cfg,
Err(e) => {
cargo::exit_with_error(e.into(), &mut Shell::new());
}
};
let result = (|| {
let args: Vec<_> = env::args_os()
.map(|s| {
s.into_string().map_err(|s| {
CliError::new(anyhow::anyhow!("invalid argument detected: {:?}", s), 1334)
})
})
.collect::<Result<_, CliError>>()?;
let flags: Flags = Flags::parse_from(args.iter());
real_main(flags, &gctxt)
})();
if let Err(e) = result { cargo::exit_with_error(e, &mut gctxt.shell())}
}