use anyhow::{anyhow, Result};
use ethers::contract::Abigen;
use proc_macro2::token_stream::IntoIter;
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::quote;
use serde_json::Value;
use std::error;
use std::io::{Read, Write};
use std::process::{Command, Stdio};
pub use {crate::contract_include as include, crate::contract_path as path};
#[macro_export]
macro_rules! contract_path {
($path:expr) => {
format!("{}/{}", std::env::var("OUT_DIR").unwrap(), $path)
};
}
#[macro_export]
macro_rules! contract_include {
($path:expr) => {
include!(concat!(env!("OUT_DIR"), "/", $path, ".rs"));
};
}
pub fn write<R, W>(
contract_name: &str,
source: R,
mut output: W,
) -> Result<(), Box<dyn error::Error>>
where
R: Read,
W: Write,
{
let source: Value = serde_json::from_reader(source)?;
let abi_source = serde_json::to_string(&source)?;
let bindings = Abigen::new(contract_name, abi_source)?.generate()?;
let tokens = bindings.into_tokens();
let tokens = self::replace_ethers_crates(tokens);
let raw = tokens.to_string();
let formatted = self::format(&raw).unwrap_or(raw);
output.write_all(formatted.as_bytes())?;
Ok(())
}
fn format<S>(source: S) -> Result<String>
where
S: AsRef<str>,
{
let mut rustfmt = Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
{
let stdin = rustfmt
.stdin
.as_mut()
.ok_or_else(|| anyhow!("stdin was not created for `rustfmt` child process"))?;
stdin.write_all(source.as_ref().as_bytes())?;
}
let output = rustfmt.wait_with_output()?;
if !output.status.success() {
return Err(anyhow!(
"`rustfmt` exited with code {}:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr),
));
}
let stdout = String::from_utf8(output.stdout)?;
Ok(stdout)
}
fn replace_ethers_crates(stream: TokenStream) -> TokenStream {
look_for_module(TokenStream::new(), &mut stream.into_iter())
}
fn look_for_module(mut new_stream: TokenStream, stream: &mut IntoIter) -> TokenStream {
match stream.next() {
None => new_stream,
Some(next) => {
let found = matches!(&next, TokenTree::Ident(ident) if ident == "mod");
new_stream.extend([next]);
if found {
look_for_group(new_stream, stream)
} else {
look_for_module(new_stream, stream)
}
}
}
}
fn look_for_group(mut new_stream: TokenStream, stream: &mut IntoIter) -> TokenStream {
match stream.next() {
None => new_stream,
Some(next) => {
let found = matches!(&next, TokenTree::Group(group) if matches!(group.delimiter(), Delimiter::Brace));
if found {
if let TokenTree::Group(group) = &next {
let group_stream =
look_for_use(TokenStream::new(), &mut group.stream().into_iter());
new_stream.extend([TokenTree::Group(Group::new(
group.delimiter(),
group_stream,
))]);
}
look_for_module(new_stream, stream)
} else {
new_stream.extend([next]);
look_for_group(new_stream, stream)
}
}
}
}
fn look_for_use(mut new_stream: TokenStream, stream: &mut IntoIter) -> TokenStream {
match stream.next() {
None => new_stream,
Some(next) => {
let found = matches!(&next, TokenTree::Ident(ident) if ident == "use");
if found {
new_stream.extend([quote!(
mod ethers_core {
pub use eth_state_fold_types::ethers::core::*;
}
mod ethers_providers {
pub use eth_state_fold_types::ethers::providers::*;
}
mod ethers_contract {
pub use eth_state_fold_types::ethers::contract::*;
}
mod ethers {
pub use eth_state_fold_types::ethers::*;
}
)]);
new_stream.extend([next]);
look_for_module(new_stream, stream)
} else {
new_stream.extend([next]);
look_for_use(new_stream, stream)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_replacing_ethers_crates_uses_correct_crates() {
let input = quote! {
mod prdel {
use ethers::whatever;
}
};
let expected_output = quote! {
mod prdel {
mod ethers_core {
pub use eth_state_fold_types::ethers::core::*;
}
mod ethers_providers {
pub use eth_state_fold_types::ethers::providers::*;
}
mod ethers_contract {
pub use eth_state_fold_types::ethers::contract::*;
}
mod ethers {
pub use eth_state_fold_types::ethers::*;
}
use ethers::whatever;
}
}
.to_string();
let actual_output = replace_ethers_crates(input).to_string();
assert_eq!(expected_output, actual_output);
}
}