tighterror-build 0.0.22

The tighterror code generation library.
Documentation
use crate::errors::{
    kind::coder::{FAILED_TO_PARSE_TOKENS, RUSTFMT_FAILED, RUSTFMT_NOT_FOUND},
    TbError,
};
use log::{error, info, warn};
use proc_macro2::TokenStream;
use regex::RegexSet;
use std::{ffi::OsStr, io::ErrorKind, process::Command};

pub fn pretty(tokens: TokenStream) -> Result<String, TbError> {
    let tokens_str = tokens.to_string();
    let file = match syn::parse2(tokens) {
        Ok(f) => f,
        Err(e) => {
            error!("syn failed to parse generated code: {e}");
            error!("bad tokens: {tokens_str}");
            return FAILED_TO_PARSE_TOKENS.into();
        }
    };
    Ok(add_newlines(prettyplease::unparse(&file)))
}

fn add_newlines(file: String) -> String {
    let rg_pre = RegexSet::new([
        r"^impl",
        r"^[[:space:]]*fn",
        r"^[[:space:]]*const fn",
        r"^[[:space:]]*pub fn",
        r"^[[:space:]]*#\[.*\]$",
        r"^[[:space:]]*impl",
    ])
    .unwrap();
    let rg_post = RegexSet::new([
        r"^[[:space:]]*}$",
        r"^[[:space:]]*];$",
        r"^[[:space:]]*impl.*\{\}$",
    ])
    .unwrap();
    let rg_comment = RegexSet::new([r"^[[:space:]]*///", r"^[[:space:]]*/\*\*"]).unwrap();
    let rg_inner_comment = RegexSet::new([r"^[[:space:]]*//!"]).unwrap();
    let mut ans = String::with_capacity(file.capacity());
    let mut last_line_is_comment = false;
    let mut last_line_is_inner_comment = false;
    let mut last_line_prefix = false;
    for line in file.lines() {
        let is_comment = rg_comment.is_match(line);
        let is_inner_comment = rg_inner_comment.is_match(line);
        let prefix = rg_pre.is_match(line)
            || (is_comment && !last_line_is_comment)
            || (!is_inner_comment && last_line_is_inner_comment);
        let postfix = rg_post.is_match(line);

        if prefix && !last_line_prefix {
            ans.push('\n');
        }
        ans.push_str(line);
        ans.push('\n');
        if postfix {
            ans.push('\n');
        }

        last_line_is_comment = is_comment;
        last_line_is_inner_comment = is_inner_comment;
        last_line_prefix = prefix;
    }
    ans
}

pub fn rustfmt(path: impl AsRef<OsStr>) -> Result<(), TbError> {
    let result = Command::new("rustfmt")
        .args(["--edition", "2021"])
        .arg(path)
        .status();
    match result {
        Ok(exit_status) => {
            if !exit_status.success() {
                warn!("rustfmt failed");
                RUSTFMT_FAILED.into()
            } else {
                Ok(())
            }
        }
        Err(e) if e.kind() == ErrorKind::NotFound => {
            info!("rustfmt not found, skipping");
            RUSTFMT_NOT_FOUND.into()
        }
        Err(e) => {
            warn!("failed to spawn rustfmt: {e}");
            RUSTFMT_FAILED.into()
        }
    }
}