synca 0.5.2

Write asynchronous code, and synca will create a synchronous version.
Documentation
use std::collections::HashMap;

use quote::ToTokens;
use syn::{punctuated::Punctuated, Expr, Token};

use crate::{replace::Replace, SyncAFold};

#[derive(Debug, PartialEq)]
pub struct SyncA {
  pub modules: HashMap<String, SyncAModule>
}

#[derive(Debug, PartialEq)]
pub struct SyncAModule {
  pub cfg: syn::Expr,
  pub item_mod: syn::ItemMod,
  pub fold: SyncAFold
}

impl syn::parse::Parse for SyncA {
  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
    let mut synca = SyncA { modules: HashMap::new() };

    let parsed = Punctuated::<syn::ItemMod, Token![,]>::parse_terminated(input)?;
    for mod_desc in parsed.iter() {
      synca.modules.insert(mod_desc.ident.clone().to_string(), mod_desc.into());
    }

    Ok(synca)
  }
}

impl From<&syn::ItemMod> for SyncAModule {
  fn from(value: &syn::ItemMod) -> Self {
    let mut item_mod = value.clone();
    if let Some(x) = &mut item_mod.content {
      x.1.clear();
    }

    let mut expr_cfg: Option<Expr> = None;
    for attr in value.attrs.iter() {
      if attr.path().is_ident("cfg") {
        expr_cfg = Some(attr.parse_args().unwrap())
      }
    }
    let cfg = match expr_cfg {
      Some(x) => x,
      None => panic!("Module {} without cfg attribute", item_mod.ident.to_string())
    };
    
    let mut fold = SyncAFold {
      module_name: item_mod.ident.to_token_stream().to_string(),
      is_async: true,
      types: HashMap::new(),
      attributes: HashMap::new(),
      cfg: cfg.clone()
    };

    for content in value.content.clone().map(|x| x.1).unwrap_or(vec![]).iter() {
      match content {
        syn::Item::Macro(m) => {
          let macro_path = m.mac.path.clone().to_token_stream().to_string();

          if m.mac.path.is_ident("sync") {
            fold.is_async = false;
            continue;
          }

          if m.mac.path.is_ident("replace") {
            Replace::new(&mut fold).apply(m).unwrap();
            continue;
          }

          panic!(
            "Unhandled module item {}: supported only sync! and replace! macro\n\n  More about it: https://synca.sgr-team.dev/usage/index.html \n\n", 
            macro_path, 
          );          
        },
        _ => panic!(
          "Unhandled module item {}: supported only sync! and replace! macro\n\n  More about it: https://synca.sgr-team.dev/usage/index.html \n\n", 
          content.clone().to_token_stream().to_string(), 
        )
      }
    }

    SyncAModule { cfg, item_mod, fold }
  }
}

#[cfg(test)]
mod tests {
  use std::collections::HashMap;

  use syn::parse_quote;

  use crate::SyncAFold;
  use super::{SyncA, SyncAModule};

  #[test]
  pub fn main() {
    let synca: SyncA = syn::parse_quote!(
      #[cfg(feature = "tokio")]
      mod my_mod_tokio { },
      #[cfg(feature = "sync")]
      mod my_mod_sync { 
        sync!();
        replace!(
          tokio_postgres::Client => postgres::Client,
          tokio_postgres::Error => postgres::Error,
          #[tokio::test] => #[test],
        );
      }
    );
    assert_eq!(
      synca,
      SyncA {
        modules: HashMap::from([
          (
            "my_mod_tokio".into(),
            SyncAModule { 
              cfg: parse_quote!(feature = "tokio"),
              item_mod: parse_quote!(#[cfg(feature = "tokio")] mod my_mod_tokio { }), 
              fold: SyncAFold { 
                module_name: "my_mod_tokio".into(), 
                is_async: true, 
                types: HashMap::new(), 
                attributes: HashMap::new(),
                cfg: parse_quote!(feature = "tokio")
              } 
            }
          ),
          (
            "my_mod_sync".into(),
            SyncAModule { 
              cfg: parse_quote!(feature = "sync"),
              item_mod: parse_quote!(#[cfg(feature = "sync")] mod my_mod_sync { }), 
              fold: SyncAFold { 
                module_name: "my_mod_sync".into(), 
                is_async: false, 
                types: HashMap::from([
                  (parse_quote!(tokio_postgres::Client), parse_quote!(postgres::Client)),
                  (parse_quote!(tokio_postgres::Error), parse_quote!(postgres::Error)),
                ]), 
                attributes: HashMap::from([
                  (parse_quote!(#[tokio::test]), parse_quote!(#[test])),
                ]),
                cfg: parse_quote!(feature = "sync")
              } 
            }
          )
        ])
      }
    )
  }
}