cido-macros 0.2.0

Macros for generating code that enables easier interfacing with cido
Documentation
mod attrs;
mod debug;
mod sql_safe_name;

pub use attrs::*;
pub use debug::*;
pub use sql_safe_name::*;

use proc_macro2::TokenStream;
use std::path::Path;

/// # Errors
/// Returns an error if the tokens can't be parsed into a `syn::File` or if writing to the file path fails
pub fn write_to_file(path: &Path, tokens: &TokenStream) -> syn::Result<()> {
  let content = tokens.to_string();

  let code = match syn::parse_file(&content) {
    Ok(output) => {
      std::panic::catch_unwind(|| prettyplease::unparse(&output)).unwrap_or_else(|panic| {
        if let Some(s) = panic.downcast_ref::<String>() {
          eprintln!("prettyplease::unparse panicked. Using raw output. Panic message: {s}");
        } else {
          eprintln!("prettyplease::unparse panicked. Using raw output");
        }
        content
      })
    }
    Err(e) => {
      eprintln!("syntax error found. Using raw token stream. Error = {e}");
      content
    }
  };

  write_string_to_file(tokens, path, &code)
}

/// # Errors
/// Returns an error if writing to the file path fails
pub fn write_string_to_file(
  span: &dyn syn::spanned::Spanned,
  path: &Path,
  content: &str,
) -> syn::Result<()> {
  use std::io::Write;
  let display_path = path.display();

  match std::fs::read_to_string(path) {
    // don't touch the file if nothing has changed to prevent unnecessary rebuilds
    Ok(existing_code) if existing_code == content => {}
    _ => {
      println!("writing to {display_path}");
      let mut file = std::fs::File::create(path).map_err(|e| {
        eprintln!("Unable to create file {display_path}. {e}");
        syn::Error::new(span.span(), e)
      })?;
      file.write_all(content.as_bytes()).map_err(|e| {
        eprintln!("Unable to write to file {display_path}. {e}");
        syn::Error::new(span.span(), e)
      })?;
      file.sync_all().map_err(|e| {
        eprintln!("Unable to sync file {display_path}. {e}");
        syn::Error::new(span.span(), e)
      })?;
    }
  }
  Ok(())
}

pub fn embed_generated_code(
  embed: bool,
  name: &syn::Ident,
  output: proc_macro2::TokenStream,
  macro_out: &str,
) -> Result<proc_macro2::TokenStream, syn::Error> {
  let embed =
    embed || std::env::var("CIDO_EMBED_GENERATED_CODE").map_or(false, |s| s == "1" || s == "true");
  if embed {
    let out_dir = std::env::var("OUT_DIR")
      .or_else(|_| std::env::var("TMPDIR"))
      .unwrap_or_else(|_| "/tmp".to_string());
    let out_file = format!("{out_dir}/{name}.{macro_out}.rs");
    println!("Including {name} instead of generating in {out_file}");
    write_to_file(out_file.as_ref(), &output).map_err(|e| {
      syn::Error::new(
        name.span(),
        format_args!("Unable to write output to file {out_file}. {e}"),
      )
    })?;
    Ok(quote::quote!(::std::include! {#out_file}))
  } else {
    Ok(output)
  }
}