synca 0.4.1

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

use syn::{Expr, Type, parse::Parse, Token, Attribute};

use crate::SyncAFold;

#[derive(Debug, PartialEq)]
pub struct SyncAInput {
  pub features: Expr,
  pub types: HashMap<Type, Type>,
  pub attributes: HashMap<Attribute, Attribute>,
}

impl SyncAInput {
  pub fn fold(self) -> SyncAFold {
    SyncAFold {
      is_async: true,
      features: self.features,
      types: self.types,
      attributes: self.attributes,
    }
  }
}

impl Parse for SyncAInput {
  fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
    let features: Expr = input.parse()?;
    let mut types = HashMap::new();
    let mut attributes = HashMap::new();

    while input.parse::<Token![,]>().is_ok() {
      if input.is_empty() {
        break;
      }

      if let Ok(async_ty) = input.parse::<Type>() {
        input.parse::<Token![=>]>()?;
        types.insert(async_ty.clone(), input.parse::<Type>()?);
        continue;
      }

      if let Ok(attrs) = Attribute::parse_outer(input) {
        if attrs.len() > 0 {
          if attrs.len() > 1 {
            panic!("SuncA expected one attribute line synca(feature = tokio, #[tokio::test] => #[test])");
          }
          let at = attrs[0].clone();
          input.parse::<Token![=>]>()?;

          let new_attrs = Attribute::parse_outer(input)?;
          if new_attrs.len() != 1 {
            panic!("SuncA expected one attribute line synca(feature = tokio, #[tokio::test] => #[test])");
          }

          attributes.insert(at.clone(), new_attrs[0].clone());
          continue;  
        }
      }
    }

    Ok(SyncAInput { features, types, attributes })
  }
}

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

  use syn::parse_quote;

  use super::SyncAInput;

  #[test]
  fn parse_one_feature() {
    let attr: SyncAInput = parse_quote!(feature = "async");

    assert_eq!(
      attr,
      SyncAInput {
        features: parse_quote!(feature = "async"),
        types: HashMap::new(),
        attributes: HashMap::new(),
      }
    );
  }

  #[test]
  fn parse_all() {
    let attr: SyncAInput = parse_quote!(all(feature = "foo", feature = "bar"));

    assert_eq!(
      attr,
      SyncAInput {
        features: parse_quote!(all(feature = "foo", feature = "bar")),
        types: HashMap::new(),
        attributes: HashMap::new(),
      }
    );
  }

  #[test]
  fn parse_not() {
    let attr: SyncAInput = parse_quote!(not(all(feature = "sync", feature = "sync_super")));
    
    assert_eq!(
      attr,
      SyncAInput {
        features: parse_quote!(not(all(feature = "sync", feature = "sync_super"))),
        types: HashMap::new(),
        attributes: HashMap::new(),
      }
    );
  }

  #[test]
  fn parse_with_types() {
    let attr: SyncAInput = parse_quote!(
      not(all(feature = "sync", feature = "sync_super")), 
      tokio_postgres::Client => postgres::Client,
      tokio_postgres::NoTls => postgres::NoTls
    );
    let attr2: SyncAInput = parse_quote!(
      not(all(feature = "sync", feature = "sync_super")), 
      tokio_postgres::Client => postgres::Client,
      tokio_postgres::NoTls => postgres::NoTls,
    );
    
    assert_eq!(
      attr,
      SyncAInput {
        features: parse_quote!(not(all(feature = "sync", feature = "sync_super"))),
        types: HashMap::from([
          (parse_quote!(tokio_postgres::Client), parse_quote!(postgres::Client)),
          (parse_quote!(tokio_postgres::NoTls), parse_quote!(postgres::NoTls))
        ]),
        attributes: HashMap::new(),
      }
    );
    assert_eq!(
      attr2,
      SyncAInput {
        features: parse_quote!(not(all(feature = "sync", feature = "sync_super"))),
        types: HashMap::from([
          (parse_quote!(tokio_postgres::Client), parse_quote!(postgres::Client)),
          (parse_quote!(tokio_postgres::NoTls), parse_quote!(postgres::NoTls))
        ]),
        attributes: HashMap::new(),
      }
    );
  }

  #[test]
  fn parse_with_types_and_attrs() {
    let attr: SyncAInput = parse_quote!(
      not(all(feature = "sync", feature = "sync_super")), 
      tokio_postgres::Client => postgres::Client,
      tokio_postgres::NoTls => postgres::NoTls,
      #[tokio::test] => #[test]
    );

    assert_eq!(
      attr,
      SyncAInput {
        features: parse_quote!(not(all(feature = "sync", feature = "sync_super"))),
        types: HashMap::from([
          (parse_quote!(tokio_postgres::Client), parse_quote!(postgres::Client)),
          (parse_quote!(tokio_postgres::NoTls), parse_quote!(postgres::NoTls))
        ]),
        attributes: HashMap::from([
          (parse_quote!(#[tokio::test]), parse_quote!(#[test])),
        ]),
      }
    );
  }
}