| 
 | 1 | +use crate::helpers;  | 
 | 2 | +use anyhow::{Result, bail};  | 
 | 3 | +use num_cpus;  | 
 | 4 | +use rayon::prelude::*;  | 
 | 5 | +use std::fs;  | 
 | 6 | +use std::io::{self, Write};  | 
 | 7 | +use std::path::Path;  | 
 | 8 | +use std::process::Command;  | 
 | 9 | +use std::sync::atomic::{AtomicUsize, Ordering};  | 
 | 10 | + | 
 | 11 | +use crate::build::packages;  | 
 | 12 | +use crate::cli::FileExtension;  | 
 | 13 | +use clap::ValueEnum;  | 
 | 14 | + | 
 | 15 | +pub fn format(  | 
 | 16 | +    stdin_extension: Option<FileExtension>,  | 
 | 17 | +    all: bool,  | 
 | 18 | +    check: bool,  | 
 | 19 | +    files: Vec<String>,  | 
 | 20 | +) -> Result<()> {  | 
 | 21 | +    let bsc_path = helpers::get_bsc();  | 
 | 22 | + | 
 | 23 | +    match stdin_extension {  | 
 | 24 | +        Some(extension) => {  | 
 | 25 | +            format_stdin(&bsc_path, extension)?;  | 
 | 26 | +        }  | 
 | 27 | +        None => {  | 
 | 28 | +            let files = if all { get_all_files()? } else { files };  | 
 | 29 | +            format_files(&bsc_path, files, check)?;  | 
 | 30 | +        }  | 
 | 31 | +    }  | 
 | 32 | + | 
 | 33 | +    Ok(())  | 
 | 34 | +}  | 
 | 35 | + | 
 | 36 | +fn get_all_files() -> Result<Vec<String>> {  | 
 | 37 | +    let current_dir = std::env::current_dir()?;  | 
 | 38 | +    let project_root = helpers::get_abs_path(¤t_dir);  | 
 | 39 | +    let workspace_root_option = helpers::get_workspace_root(&project_root);  | 
 | 40 | + | 
 | 41 | +    let build_state = packages::make(&None, &project_root, &workspace_root_option, false, false)?;  | 
 | 42 | +    let mut files: Vec<String> = Vec::new();  | 
 | 43 | + | 
 | 44 | +    for (_package_name, package) in build_state {  | 
 | 45 | +        if let Some(source_files) = package.source_files {  | 
 | 46 | +            for (path, _metadata) in source_files {  | 
 | 47 | +                if let Some(extension) = path.extension() {  | 
 | 48 | +                    if extension == "res" || extension == "resi" {  | 
 | 49 | +                        files.push(package.path.join(path).to_string_lossy().into_owned());  | 
 | 50 | +                    }  | 
 | 51 | +                }  | 
 | 52 | +            }  | 
 | 53 | +        }  | 
 | 54 | +    }  | 
 | 55 | +    Ok(files)  | 
 | 56 | +}  | 
 | 57 | + | 
 | 58 | +fn format_stdin(bsc_exe: &Path, extension: FileExtension) -> Result<()> {  | 
 | 59 | +    let extension_value = extension  | 
 | 60 | +        .to_possible_value()  | 
 | 61 | +        .ok_or(anyhow::anyhow!("Could not get extension arg value"))?;  | 
 | 62 | + | 
 | 63 | +    let mut temp_file = tempfile::Builder::new()  | 
 | 64 | +        .suffix(extension_value.get_name())  | 
 | 65 | +        .tempfile()?;  | 
 | 66 | +    io::copy(&mut io::stdin(), &mut temp_file)?;  | 
 | 67 | +    let temp_path = temp_file.path();  | 
 | 68 | + | 
 | 69 | +    let mut cmd = Command::new(bsc_exe);  | 
 | 70 | +    cmd.arg("-format").arg(temp_path);  | 
 | 71 | + | 
 | 72 | +    let output = cmd.output()?;  | 
 | 73 | + | 
 | 74 | +    if output.status.success() {  | 
 | 75 | +        io::stdout().write_all(&output.stdout)?;  | 
 | 76 | +    } else {  | 
 | 77 | +        let stderr_str = String::from_utf8_lossy(&output.stderr);  | 
 | 78 | +        bail!("Error formatting stdin: {}", stderr_str);  | 
 | 79 | +    }  | 
 | 80 | + | 
 | 81 | +    Ok(())  | 
 | 82 | +}  | 
 | 83 | + | 
 | 84 | +fn format_files(bsc_exe: &Path, files: Vec<String>, check: bool) -> Result<()> {  | 
 | 85 | +    let batch_size = 4 * num_cpus::get();  | 
 | 86 | +    let incorrectly_formatted_files = AtomicUsize::new(0);  | 
 | 87 | + | 
 | 88 | +    files.par_chunks(batch_size).try_for_each(|batch| {  | 
 | 89 | +        batch.iter().try_for_each(|file| {  | 
 | 90 | +            let mut cmd = Command::new(bsc_exe);  | 
 | 91 | +            if check {  | 
 | 92 | +                cmd.arg("-format").arg(file);  | 
 | 93 | +            } else {  | 
 | 94 | +                cmd.arg("-o").arg(file).arg("-format").arg(file);  | 
 | 95 | +            }  | 
 | 96 | + | 
 | 97 | +            let output = cmd.output()?;  | 
 | 98 | + | 
 | 99 | +            if output.status.success() {  | 
 | 100 | +                if check {  | 
 | 101 | +                    let original_content = fs::read_to_string(file)?;  | 
 | 102 | +                    let formatted_content = String::from_utf8_lossy(&output.stdout);  | 
 | 103 | +                    if original_content != formatted_content {  | 
 | 104 | +                        eprintln!("[format check] {}", file);  | 
 | 105 | +                        incorrectly_formatted_files.fetch_add(1, Ordering::SeqCst);  | 
 | 106 | +                    }  | 
 | 107 | +                }  | 
 | 108 | +            } else {  | 
 | 109 | +                let stderr_str = String::from_utf8_lossy(&output.stderr);  | 
 | 110 | +                bail!("Error formatting {}: {}", file, stderr_str);  | 
 | 111 | +            }  | 
 | 112 | +            Ok(())  | 
 | 113 | +        })  | 
 | 114 | +    })?;  | 
 | 115 | + | 
 | 116 | +    let count = incorrectly_formatted_files.load(Ordering::SeqCst);  | 
 | 117 | +    if count > 0 {  | 
 | 118 | +        if count == 1 {  | 
 | 119 | +            eprintln!("The file listed above needs formatting");  | 
 | 120 | +        } else {  | 
 | 121 | +            eprintln!("The {} files listed above need formatting", count);  | 
 | 122 | +        }  | 
 | 123 | +        bail!("Formatting check failed");  | 
 | 124 | +    }  | 
 | 125 | + | 
 | 126 | +    Ok(())  | 
 | 127 | +}  | 
0 commit comments