use crate::log::Log;
use anyhow::Ok;
use clap::Subcommand;
use crax_monorepo::ctx;
use delete::{delete_file_async, delete_folder_all};
use dialoguer::{theme::ColorfulTheme, MultiSelect};
use globset::{Glob, GlobSet, GlobSetBuilder};
use jwalk::WalkDir;
use loading::Loading;
use owo_colors::OwoColorize;
use path_absolutize::*;
use std::{fs, path::PathBuf, time::Instant};
use tokio::task::JoinSet;
const DEF_WS: &'static str = "packages:
- packages/*
";
#[derive(Subcommand)]
pub enum Cmd {
#[command(long_about = "Clear any *-lock.{json,yaml} and remove all node_modules.")]
Cls {
#[arg(short, long, help = "Directory you will walk")]
dir: Option<String>,
#[arg(short, long, default_value_t = false, help = "Weather include lock file")]
lock: bool,
#[arg(short, long, help = "Ignore pattern see: https://crates.io/crates/globset")]
ignore: Option<String>,
},
#[command(long_about = "Add pnpm-workspace file.")]
W {
#[arg(short, long, help = "Directory you will walk")]
dir: Option<String>,
},
}
fn is_hidden(fname: Option<&str>) -> bool {
fname.map(|s| s.starts_with(".")).unwrap_or(false) || fname.unwrap().to_string() == "target"
}
fn create_gs(glob: &str) -> Result<GlobSet, anyhow::Error> {
let mut builder = GlobSetBuilder::new();
builder.add(Glob::new(glob)?);
let set = builder.build()?;
Ok(set)
}
async fn rm(v: String) {
let p = PathBuf::from(v);
let n = p.display().to_string();
if p.is_dir() {
delete_folder_all(&n).unwrap();
}
if p.is_file() {
delete_file_async(&n).await.unwrap();
}
}
pub async fn run(ctx: &mut ctx::Ctx, command: &Option<Cmd>) -> anyhow::Result<()> {
match command {
Some(Cmd::Cls { dir, lock, ignore }) => {
let cwd = match dir {
Some(d) => {
let p = PathBuf::from(d);
match p.is_relative() {
true => ctx.cwd.join(p),
false => p,
}
}
None => ctx.cwd.clone(),
};
let gsf = create_gs("**/{pnpm,package,yarn}-lock.{json,yaml}")?;
let gsd = create_gs("**/node_modules")?;
let gig: Option<_> =
if ignore.is_some() { Some(create_gs(ignore.as_ref().unwrap())?) } else { None };
let mut v: Vec<String> = vec![];
let loading = Loading::default();
let start_time = Instant::now();
let mut size: usize = 0;
let mut skip: usize = 0;
for entry in WalkDir::new(cwd.absolutize()?) {
size += 1;
let path = entry.unwrap().path();
let name = path.display().to_string();
if is_hidden(path.file_name().unwrap_or_default().to_str()) {
skip += 1;
continue;
};
if let Some(gb) = gig.as_ref() {
if gb.is_match(&path) {
skip += 1;
continue;
}
}
if v.iter().any(|base| name.starts_with(base)) {
continue;
}
if gsd.is_match(&path) {
v.push(name);
continue;
}
if *lock && gsf.is_match(&path) {
v.push(name);
continue;
}
}
loading.end();
let defaults: Vec<bool> = v.iter().enumerate().map(|_| false).collect();
let prompt_str = format!(
"In: {}, Total: {}, Find: {}, Skip: {}, Time: {}ms. Press {} give up {} select all",
cwd.display(),
size,
v.len(),
skip,
start_time.elapsed().as_millis(),
"q".green(),
"a".green()
);
if !v.is_empty() {
let mut tasks = JoinSet::new();
let selects = MultiSelect::with_theme(&ColorfulTheme::default())
.report(false)
.with_prompt(prompt_str)
.items(&v[..])
.defaults(&defaults[..])
.interact_opt()?;
if let Some(selects) = selects {
let start_time = Instant::now();
let loading = Loading::default();
for s in selects {
tasks.spawn(rm((&v[s]).clone()));
}
tasks.join_all().await;
loading.end();
Log::Suc(&format!("Done: {}ms", start_time.elapsed().as_millis())).println();
return Ok(());
} else {
}
}
Ok(())
}
Some(Cmd::W { dir }) => {
let cwd = match dir {
Some(d) => {
let p = PathBuf::from(d);
match p.is_relative() {
true => ctx.cwd.join(p),
false => p,
}
}
None => ctx.cwd.clone(),
};
let p_ws = cwd.join("pnpm-workspace.yaml");
if !p_ws.exists() {
fs::write(p_ws, DEF_WS)?
}
Ok(())
}
None => Err(anyhow::Error::msg("Command not found!")),
}
}