use std::{
env,
fs::{self, File},
io::{Read, Write},
path::PathBuf,
result::Result as StdResult,
};
use anyhow::Result;
use clap::{Args, Parser, Subcommand, ValueEnum};
use regex::bytes::{Captures, Regex};
use walkdir::{DirEntry, WalkDir};
static mut VERBOSE: bool = false;
fn main() -> Result<()> {
let mut args = env::args();
if let Some("all") = env::args().nth(1).as_deref() {
args.next();
}
Cli::parse_from(args).run()?;
Ok(())
}
#[derive(Debug, Parser)]
#[command(
version = concat!(
env!("VERGEN_BUILD_SEMVER"),
"-",
env!("VERGEN_GIT_SHA_SHORT"),
"-",
env!("VERGEN_CARGO_TARGET_TRIPLE"),
),
about,
rename_all = "kebab",
)]
struct Cli {
#[command(subcommand)]
subcmd: Subcmd,
#[arg(global = true, long, short)]
verbose: bool,
}
impl Cli {
fn run(self) -> Result<()> {
tracing_subscriber::fmt::init();
let Self { subcmd, verbose } = self;
unsafe {
VERBOSE = verbose;
}
subcmd.run()?;
Ok(())
}
}
#[derive(Debug, Subcommand)]
enum Subcmd {
SetToolchain(SetToolchainCmd),
Clean(CleanCmd),
}
impl Subcmd {
fn run(self) -> Result<()> {
use Subcmd::*;
match self {
SetToolchain(c) => c.run(),
Clean(c) => c.run(),
}?;
Ok(())
}
}
#[derive(Debug, Args)]
struct SetToolchainCmd {
#[arg(value_name = "CHANNEL")]
channel: String,
}
impl SetToolchainCmd {
fn run(self) -> Result<()> {
let Self { channel } = self;
let verbose = unsafe { VERBOSE };
let regex = Regex::new(r#"( *channel *= *)".*""#).unwrap();
let set_version = |p: PathBuf| {
if verbose {
tracing::info!("setting: {}", p.display());
}
match File::open(&p) {
Ok(mut f) => {
let mut v = Vec::new();
if let Err(e) = f.read_to_end(&mut v) {
tracing::error!("skipped due to, {e:?}");
}
let v = regex.replace(&v, |c: &Captures| {
let mut r = c[1].to_owned();
r.push(b'"');
r.extend(channel.as_bytes());
r.push(b'"');
r
});
let p_tmp = p.with_extension("cargo-all");
match File::create(&p_tmp) {
Ok(mut f) => {
if let Err(e) = f.write_all(&v) {
tracing::error!("skipped due to, {e:?}");
}
if let Err(e) = fs::rename(&p_tmp, p) {
tracing::error!("skipped due to, {e:?}");
if let Err(e) = fs::remove_file(p_tmp) {
tracing::error!("failed to remove tmp file due to, {e:?}");
}
}
},
Err(e) => tracing::error!("skipped due to, {e:?}"),
}
},
Err(e) => tracing::error!("skipped due to, {e:?}"),
}
};
walk_with(&["rust-toolchain.toml", "rust-toolchain"], set_version)?;
Ok(())
}
}
#[derive(Debug, Args)]
struct CleanCmd {
#[arg(value_enum, value_name = "NAME", default_value = "debug")]
profile: Profile,
}
impl CleanCmd {
fn run(self) -> Result<()> {
let Self { profile } = self;
let verbose = unsafe { VERBOSE };
let rm = |p: PathBuf| {
if let Err(e) = fs::remove_dir_all(p) {
if verbose {
tracing::warn!("skipped due to, {e:?}");
}
}
};
let rm_all = |p: PathBuf| {
let p = p.parent().expect("already checked in previous step; qed").join("target");
if verbose {
tracing::info!("removing: {}", p.display());
}
rm(p);
};
let rm_profile = |p: PathBuf| {
let p = p
.parent()
.expect("already checked in previous step; qed")
.join("target")
.join(profile.as_str());
if verbose {
tracing::info!("removing: {}", p.display());
}
rm(p);
};
match profile {
Profile::All => walk_with(&["Cargo.toml"], rm_all)?,
_ => walk_with(&["Cargo.toml"], rm_profile)?,
}
Ok(())
}
}
#[derive(Clone, Debug, ValueEnum)]
enum Profile {
Debug,
Release,
All,
}
impl Profile {
fn as_str(&self) -> &'static str {
use Profile::*;
match self {
Debug => "debug",
Release => "release",
All => unreachable!(),
}
}
}
fn walk_with<F>(targets: &[&str], f: F) -> Result<()>
where
F: Fn(PathBuf),
{
let to_filter = |e: &DirEntry| {
let n = e.file_name().to_string_lossy();
!(n.starts_with('.') || n == "target")
};
let to_match = |r: StdResult<DirEntry, _>| match r {
Ok(e) => {
let n = e.file_name().to_string_lossy();
if targets.iter().any(|t| *t == n) {
Some(e.path().to_path_buf())
} else {
None
}
},
Err(e) => {
tracing::error!("skipped due to, {e:?}");
None
},
};
for e in
WalkDir::new(env::current_dir()?).into_iter().filter_entry(to_filter).filter_map(to_match)
{
f(e);
}
Ok(())
}