Skip to content

Instantly share code, notes, and snippets.

@qryxip
Created January 29, 2023 07:48
Show Gist options
  • Save qryxip/84722499d5187215c8f3f54b7a171997 to your computer and use it in GitHub Desktop.
Save qryxip/84722499d5187215c8f3f54b7a171997 to your computer and use it in GitHub Desktop.
AtCoder 2023/1 Language Update用の情報を生成する
#!/usr/bin/env rust-script
//! ```cargo
//! [package]
//! edition = "2021"
//! license = "CC0-1.0"
//!
//! [dependencies]
//! camino = "1.1.2"
//! cargo_metadata = "0.15.2"
//! clap = { version = "4.1.4", features = ["derive"] }
//! color-eyre = "0.6.2"
//! eyre = "0.6.8"
//! fs-err = "2.9.0"
//! indexmap = { version = "1.9.2", features = ["serde-1"] }
//! itertools = "0.10.5"
//! serde = { version = "1.0.152", features = ["derive"] }
//! toml = "0.7.0"
//! ```
use std::collections::HashMap;
use camino::Utf8Path;
use cargo_metadata::{CargoOpt, Dependency, DependencyKind, MetadataCommand, Package};
use clap::Parser as _;
use eyre::{ensure, eyre};
use indexmap::IndexMap;
use itertools::Itertools as _;
use serde::Deserialize;
#[allow(clippy::enum_variant_names)]
#[derive(clap::Parser)]
enum Args {
GenSpecs(ArgsGenSpecs),
GenCommand(ArgsGenCommand),
GenLicenseUrls(ArgsGenLicenseUrls),
}
#[derive(clap::Parser)]
struct ArgsGenSpecs {}
#[derive(clap::Parser)]
struct ArgsGenCommand {}
#[derive(clap::Parser)]
struct ArgsGenLicenseUrls {}
fn main() -> eyre::Result<()> {
color_eyre::install()?;
match Args::parse() {
Args::GenSpecs(args) => gen_specs(args),
Args::GenCommand(args) => gen_command(args),
Args::GenLicenseUrls(args) => gen_license_urls(args),
}
}
fn gen_specs(ArgsGenSpecs {}: ArgsGenSpecs) -> eyre::Result<()> {
let md = MetadataCommand::new()
.features(CargoOpt::AllFeatures)
.exec()?;
let root_package = &md.root_package().ok_or_else(|| eyre!("no root package"))?;
let specs = normal_crates_io_deps(root_package)?
.map(|Dependency { name, req, .. }| (&**name, format!("{name}@{req}")))
.collect();
for spec in reorder(specs, &root_package.manifest_path)? {
println!("{spec}");
}
Ok(())
}
fn gen_command(ArgsGenCommand {}: ArgsGenCommand) -> eyre::Result<()> {
let md = MetadataCommand::new()
.features(CargoOpt::AllFeatures)
.exec()?;
let root_package = &md.root_package().ok_or_else(|| eyre!("no root package"))?;
let source_args = normal_crates_io_deps(root_package)?
.map(
|Dependency {
name,
req,
features,
..
}| {
let spec = format!("{name}@{req}");
let mut args = spec.clone();
if !features.is_empty() {
args += " --features ";
args += &features.iter().map(|f| format!("{spec}/{f}")).join(",")
}
(&**name, args)
},
)
.collect();
println!(r"cargo add \");
let mut package_args = reorder(source_args, &root_package.manifest_path)?.peekable();
while let Some(package_args_) = package_args.next() {
print!(" {package_args_}");
if package_args.peek().is_some() {
print!(r" \");
}
println!();
}
Ok(())
}
fn gen_license_urls(ArgsGenLicenseUrls {}: ArgsGenLicenseUrls) -> eyre::Result<()> {
let md = MetadataCommand::new()
.features(CargoOpt::AllFeatures)
.exec()?;
let root_package = &md.root_package().ok_or_else(|| eyre!("no root package"))?;
let packages = md
.packages
.iter()
.map(|p| ((&*p.name, p.version.to_string()), p))
.collect::<HashMap<_, _>>();
let urls = normal_crates_io_deps(root_package)?
.map(|Dependency { name, req, .. }| {
let version = req.to_string().trim_start_matches('=').to_owned();
let Package {
name,
version,
manifest_path,
..
} = packages[&(&**name, version)];
let manifest_dir = manifest_path.parent().unwrap();
let url = format!("https://docs.rs/crate/{name}/{version}/source/");
let url = if manifest_dir.join("LICENSE").exists() {
format!("{url}LICENSE")
} else {
url
};
(&**name, url)
})
.collect();
for url in reorder(urls, &root_package.manifest_path)? {
println!("{url}");
}
Ok(())
}
fn normal_crates_io_deps(
root_package: &Package,
) -> eyre::Result<impl Iterator<Item = &Dependency>> {
root_package
.dependencies
.iter()
.filter(|Dependency { source, kind, .. }| {
source.as_deref() == Some("registry+https://github.com/rust-lang/crates.io-index")
&& *kind == DependencyKind::Normal
})
.map(|dep| {
ensure!(dep.uses_default_features, "not yet suppoorted");
ensure!(!dep.optional, "not yet suppoorted");
ensure!(dep.target.is_none(), "not yet suppoorted");
ensure!(dep.rename.is_none(), "not yet suppoorted");
Ok(dep)
})
.collect::<Result<Vec<_>, _>>()
.map(IntoIterator::into_iter)
}
fn reorder<'a, V: 'a>(
items: HashMap<&'a str, V>,
manifest_path: &Utf8Path,
) -> eyre::Result<impl Iterator<Item = V> + 'a> {
let Manifest { dependencies } = toml::from_str(&fs_err::read_to_string(manifest_path)?)?;
return Ok(items
.into_iter()
.sorted_by_key(move |(name, _)| {
dependencies
.keys()
.enumerate()
.find(|&(_, name_)| name_ == name)
.map(|(i, _)| i)
})
.map(|(_, v)| v));
#[derive(Deserialize)]
struct Manifest {
dependencies: IndexMap<String, toml::Value>,
}
}
@qryxip
Copy link
Author

qryxip commented Mar 26, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment