diff --git a/Cargo.lock b/Cargo.lock index 79cd4c5..8a7a604 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.69" @@ -16,10 +25,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cargo-all" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "clap", + "once_cell", + "regex", "tracing", "tracing-subscriber", "vergen", @@ -269,6 +280,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "ntapi" version = "0.4.0" @@ -366,6 +383,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rustc_version" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index c90118e..e4da0d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "GPL-3.0" name = "cargo-all" readme = "README.md" repository = "https://github.com/hack-ink/cargo-all" -version = "0.1.0" +version = "0.2.0" [build-dependencies] vergen = { version = "7.5.0" } @@ -16,6 +16,8 @@ vergen = { version = "7.5.0" } [dependencies] anyhow = { version = "1.0" } clap = { version = "4.1", features = ["derive"] } +once_cell = { version = "1.17" } +regex = { version = "1.7" } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } walkdir = { version = "2.3" } diff --git a/src/main.rs b/src/main.rs index 7e8f822..3723d65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,31 @@ // std -use std::{env, fs, path::PathBuf, result::Result as StdResult}; +use std::{ + env, + fs::{self, File}, + io::{Read, Write}, + path::PathBuf, + result::Result as StdResult, +}; // crates.io 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!( @@ -20,12 +41,20 @@ use walkdir::{DirEntry, WalkDir}; struct Cli { #[command(subcommand)] subcmd: Subcmd, + #[arg(global = true, long, short)] + verbose: bool, } impl Cli { fn run(self) -> Result<()> { tracing_subscriber::fmt::init(); - self.subcmd.run()?; + let Self { subcmd, verbose } = self; + + unsafe { + VERBOSE = verbose; + } + + subcmd.run()?; Ok(()) } @@ -33,7 +62,7 @@ impl Cli { #[derive(Debug, Subcommand)] enum Subcmd { - Toolchain(ToolchainCmd), + SetToolchain(SetToolchainCmd), Clean(CleanCmd), } impl Subcmd { @@ -42,7 +71,7 @@ impl Subcmd { use Subcmd::*; match self { - Toolchain(c) => c.run(), + SetToolchain(c) => c.run(), Clean(c) => c.run(), }?; @@ -51,9 +80,65 @@ impl Subcmd { } #[derive(Debug, Args)] -struct ToolchainCmd {} -impl ToolchainCmd { +struct SetToolchainCmd { + /// Set toolchain channel. + /// + /// e.g. "nightly-2023-01-01" + #[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(()) } } @@ -61,69 +146,46 @@ impl ToolchainCmd { #[derive(Debug, Args)] struct CleanCmd { /// Profile. - #[arg(value_enum, long, short, value_name = "NAME", default_value = "debug")] + #[arg(value_enum, value_name = "NAME", default_value = "debug")] profile: Profile, } impl CleanCmd { fn run(self) -> Result<()> { - fn walk_with(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| match r { - Ok(e) => - if e.file_name().to_string_lossy().ends_with("Cargo.toml") { - e.path().parent().map(|p| p.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(()) - } - let Self { profile } = self; + let verbose = unsafe { VERBOSE }; let rm = |p: PathBuf| { if let Err(e) = fs::remove_dir_all(p) { - tracing::warn!("skipped due to, {e:?}"); + if verbose { + tracing::warn!("skipped due to, {e:?}"); + } } }; let rm_all = |p: PathBuf| { - let p = p.join("target"); + let p = p.parent().expect("already checked in previous step; qed").join("target"); - tracing::info!("removing: {}", p.display()); + if verbose { + tracing::info!("removing: {}", p.display()); + } rm(p); }; let rm_profile = |p: PathBuf| { - let p = p.join("target").join(profile.as_str()); - - tracing::info!("removing: {}", p.display()); + 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(rm_all)?, - _ => walk_with(rm_profile)?, + Profile::All => walk_with(&["Cargo.toml"], rm_all)?, + _ => walk_with(&["Cargo.toml"], rm_profile)?, } Ok(()) @@ -149,10 +211,37 @@ impl Profile { } } -fn main() -> Result<()> { - let cli = Cli::parse(); - - cli.run()?; +fn walk_with(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| 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(()) }