feach 0.1.2

This tool can foreach dirs and run command.
use clap::Parser;
use colored::*;
use std::process::Command;
use walkdir::WalkDir;

#[derive(Parser, Debug)]
#[command(
    author,
    version = "0.1.1",
    about = "This tool can foreach dirs and run command.",
    long_about
)]
struct Args {
    #[arg(short, long, default_value = "./")]
    dir: String,

    #[arg(short, long, num_args = 1..)]
    cmd: Vec<String>,

    #[arg(short, long, default_value_t = 1)]
    layers: u8,

    #[arg(short, long)]
    r#async: bool,
}

fn parse_cmd_args(cmd: &str) -> Vec<String> {
    let mut args = Vec::new();
    let mut current = String::new();
    let mut in_single_quote = false;
    let mut in_double_quote = false;

    for c in cmd.chars() {
        match c {
            '\'' if !in_double_quote => in_single_quote = !in_single_quote,
            '"' if !in_single_quote => in_double_quote = !in_double_quote,
            ' ' if !in_single_quote && !in_double_quote => {
                if !current.is_empty() {
                    args.push(std::mem::take(&mut current));
                }
            }
            _ => current.push(c),
        }
    }
    if !current.is_empty() {
        args.push(current);
    }
    args
}

fn do_command4_dir_async(work_dir: String, cmd_program: String, cmd_args: Vec<String>) {
    tokio::spawn(async { do_command4_dir(work_dir, cmd_program, cmd_args) });
}

fn do_command4_dir(work_dir: String, cmd_program: String, cmd_args: Vec<String>) {
    let output = Command::new(cmd_program)
        .args(cmd_args)
        .current_dir(work_dir)
        .spawn()
        .unwrap()
        .wait_with_output()
        .expect("failed to execute process");
    println!("{}", format!("{:?}", output).purple());
}

#[tokio::main]
async fn main() {
    let args = Args::parse();
    WalkDir::new(args.dir)
        .max_depth(args.layers.into())
        .follow_links(true)
        .into_iter()
        .filter_map(|result| result.ok())
        .into_iter()
        .filter(|file| file.file_type().is_dir())
        .for_each(|dir| {
            let work_dir: String = dir.path().to_str().unwrap().to_string();
            let cmd_str = args.cmd.join(" ");
            let mut cmd_args = parse_cmd_args(&cmd_str);
            let cmd_program: String = cmd_args.remove(0).to_string();
            println!(
                "{} {} -> {} {}",
                "[RUN COMMAND]".on_bright_blue(),
                work_dir,
                cmd_program.bright_blue(),
                cmd_args.join(" ").yellow()
            );
            match args.r#async {
                true => do_command4_dir_async(work_dir, cmd_program, cmd_args),
                false => do_command4_dir(work_dir, cmd_program, cmd_args),
            }
        });
}