use crate::{
project::{MetadataExt as _, PackageExt as _},
shell::ColorChoice,
web::ATCODER_RUST_LANG_ID,
};
use anyhow::{anyhow, Context as _};
use if_chain::if_chain;
use ignore::{overrides::OverrideBuilder, WalkBuilder};
use itertools::Itertools as _;
use snowchains_core::web::PlatformKind;
use std::{
iter,
path::{Path, PathBuf},
};
use structopt::StructOpt;
use strum::VariantNames as _;
use termcolor::Color;
#[derive(StructOpt, Debug)]
pub struct OptCompeteMigrateCargoAtcoder {
#[structopt(long)]
pub glob_case_insensitive: bool,
#[structopt(short, long, value_name("GLOB"))]
pub glob: Vec<String>,
pub cargo_atcoder_config: Option<PathBuf>,
#[structopt(
long,
value_name("WHEN"),
possible_values(ColorChoice::VARIANTS),
default_value("auto")
)]
pub color: ColorChoice,
#[structopt(default_value("."))]
pub path: PathBuf,
}
pub(crate) fn run(
opt: OptCompeteMigrateCargoAtcoder,
ctx: crate::Context<'_>,
) -> anyhow::Result<()> {
let OptCompeteMigrateCargoAtcoder {
glob_case_insensitive,
glob,
cargo_atcoder_config,
color,
path,
} = opt;
let crate::Context {
cwd,
cookies_path: _,
shell,
} = ctx;
shell.set_color_choice(color);
let path = cwd.join(path.strip_prefix(".").unwrap_or(&path));
let manifest_paths = WalkBuilder::new(&path)
.follow_links(true)
.max_depth(Some(32))
.overrides({
let mut overrides = OverrideBuilder::new(&path);
for glob in glob {
overrides.add(&glob)?;
}
overrides.case_insensitive(glob_case_insensitive)?.build()?
})
.build()
.map(|entry| {
let manifest_path = entry?.into_path();
Ok(
if manifest_path.file_name() == Some("Cargo.toml".as_ref()) {
Some(manifest_path)
} else {
None
},
)
})
.flat_map(Result::transpose)
.collect::<Result<Vec<_>, ignore::Error>>()?;
let mut packages = vec![];
for manifest_path in manifest_paths.into_iter().sorted() {
let metadata = crate::project::cargo_metadata_no_deps(&manifest_path, &cwd)?;
if_chain! {
if let [package] = *metadata.all_members();
if package.manifest_path == manifest_path;
then {
shell.status("Found", format_args!("`{}`", manifest_path.display()))?;
packages.push(package.clone());
} else {
shell.status_with_color(
"Ignoring",
format_args!("`{}`", manifest_path.display()),
Color::Cyan,
)?;
}
}
}
for package in &packages {
let mut manifest =
crate::fs::read_to_string(&package.manifest_path)?.parse::<toml_edit::Document>()?;
let bins = package.all_bin_targets_sorted();
if { &mut manifest["package"]["metadata"]["cargo-compete"] }.is_none() {
manifest["package"]["metadata"] = implicit_table();
manifest["package"]["metadata"]["cargo-compete"] = implicit_table();
manifest["package"]["metadata"]["cargo-compete"]["config"] = toml_edit::value({
let manifest_dir = package.manifest_path.with_file_name("");
if let Ok(rel_manifest_dir) = manifest_dir.strip_prefix(&path) {
rel_manifest_dir
.iter()
.map(|_| "..")
.chain(iter::once("compete.toml"))
.join(std::path::MAIN_SEPARATOR_STR)
} else {
manifest_dir
.into_os_string()
.into_string()
.map_err(|s| anyhow!("invalid utf-8 path: {:?}", s))?
}
});
manifest["package"]["metadata"]["cargo-compete"]["bin"] = toml_edit::Item::Table({
let mut tbl = toml_edit::Table::new();
for bin in &bins {
tbl[&bin.name]["name"] =
toml_edit::value(format!("{}-{}", package.name, bin.name));
tbl[&bin.name]["problem"] = toml_edit::value(format!(
"https://atcoder.jp/contests/{}/<FIXME: screen name of the problem>",
package.name,
));
}
tbl
});
}
if { &mut manifest["bin"] }.is_none() {
manifest["bin"] = toml_edit::Item::ArrayOfTables({
let mut arr = toml_edit::ArrayOfTables::new();
for bin in bins {
let mut tbl = toml_edit::Table::new();
tbl["name"] = toml_edit::value(format!("{}-{}", package.name, bin.name));
tbl["path"] = toml_edit::value(format!("./src/bin/{}.rs", bin.name));
arr.push(tbl);
}
arr
});
}
crate::fs::write(&package.manifest_path, manifest.to_string())?;
shell.status("Modified", &package.manifest_path)?;
}
for package in &packages {
let lock_path = &package.manifest_path.with_file_name("Cargo.lock");
shell.status("Updating", lock_path)?;
if let Err(err) = crate::project::cargo_metadata(&package.manifest_path, &cwd) {
shell.warn(format!("broke `{lock_path}`!!!!!: {err}"))?;
}
}
crate::fs::create_dir_all(path.join(".cargo"))?;
let cargo_config_path = path.join(".cargo").join("config.toml");
let mut cargo_config = if cargo_config_path.exists() {
crate::fs::read_to_string(&cargo_config_path)?
} else {
r#"[build]
target-dir = ""
"#
.to_owned()
}
.parse::<toml_edit::Document>()
.with_context(|| {
format!(
"could not parse the TOML file at `{}`",
cargo_config_path.display(),
)
})?;
if { &mut cargo_config["build"]["target-dir"] }.is_none() {
cargo_config["build"]["target-dir"] = toml_edit::value("../target");
crate::fs::write(&cargo_config_path, cargo_config.to_string())?;
shell.status("Wrote", cargo_config_path.display())?;
}
let cargo_atcoder_config = (|| -> _ {
fn parse(path: &Path) -> anyhow::Result<toml_edit::Document> {
crate::fs::read_to_string(path)?.parse().with_context(|| {
format!(
"could not parse the cargo-atcoder config at `{}`",
path.display()
)
})
}
if let Some(cargo_atcoder_config) = cargo_atcoder_config {
let cargo_atcoder_config = cwd.join(cargo_atcoder_config);
if cargo_atcoder_config.exists() {
return parse(&cargo_atcoder_config).map(Some);
}
}
if let Some(config_dir) = dirs_next::config_dir() {
let cargo_atcoder_config = config_dir.join("cargo-atcoder.toml");
if cargo_atcoder_config.exists() {
return parse(&cargo_atcoder_config).map(Some);
}
}
Ok(None)
})()?;
let submit_via_binary = matches!(
&cargo_atcoder_config,
Some(c) if c["atcoder"]["submit_via_binary"].as_bool() == Some(true)
);
let compete_toml_path = path.join("compete.toml");
let compete_toml = crate::config::generate(
"2018",
cargo_atcoder_config
.as_ref()
.and_then(|c| c["dependencies"].as_table())
.map(|t| t.to_string())
.as_deref(),
None,
PlatformKind::Atcoder,
"1.42.0",
submit_via_binary,
ATCODER_RUST_LANG_ID,
)?;
crate::fs::write(&compete_toml_path, compete_toml)?;
shell.status("Wrote", compete_toml_path.display())?;
shell.status("Finished", "migrating")?;
Ok(())
}
fn implicit_table() -> toml_edit::Item {
let mut tbl = toml_edit::Table::new();
tbl.set_implicit(true);
toml_edit::Item::Table(tbl)
}