1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
//! # `antlion`
//!
//! A magical _meta_ function that evaluate (at compile-time if used inside a
//! macro which is the point of taking a `TokenStream` input) any Rust expr!
//!
//! ## Example
//!
//! ```rust
//! use antlion::Sandbox;
//! use quote::quote;
//!
//! let test = Sandbox::new("calc").unwrap();
//! let x: u32 = test.eval(quote! { 2 + 2 }).unwrap();
//! assert!(x == 4);
//! ```
//!
//! This library indeed is not what would benefit the most your crate build
//! time, but it was still design in mind with the will of caching sandbox
//! compilation.
//!
//! ## Acknowledgments
//!
//! ⚠️ This is still a working experiment, not yet production ready.
//!
//! This project was part of a work assignment as an
//! [IOG](https://github.com/input-output-hk) contractor.
//!
//! ## License
//!
//! Licensed under either of [Apache License](LICENSE-APACHE), Version 2.0 or
//! [MIT license](LICENSE-MIT) at your option.
//!
//! Unless you explicitly state otherwise, any contribution intentionally submitted
//! for inclusion in this project by you, as defined in the Apache-2.0 license,
//! shall be dual licensed as above, without any additional terms or conditions.
#![forbid(unsafe_code)]
use proc_macro2::TokenStream;
use quote::quote;
use std::io::Result;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use std::sync::Mutex;
use std::{env, fs, io};
/// Internal representation of a `Sandbox`
///
/// A `Sandbox` is a throwable Cargo project made to evaluate arbitrary Rust
/// expression.
#[non_exhaustive]
pub struct Sandbox {
lock: Mutex<()>,
root_dir: PathBuf,
}
impl Sandbox {
/// Create a `Sandbox` in `$OUT_DIR` folder
///
/// `$OUT_DIR` is set by Cargo when `build.rs` is present :)
pub fn new(uuid: &str) -> Result<Self> {
let out_dir = env!("OUT_DIR");
let mut root_dir = PathBuf::from(out_dir);
root_dir.push(uuid);
Command::new("mkdir")
.args(["-p", root_dir.to_str().unwrap()])
.output()?;
Command::new("cargo")
.current_dir(&root_dir)
.args(["new", "sandbox"])
.output()?;
root_dir.push("sandbox");
Ok(Sandbox {
root_dir,
lock: Mutex::new(()),
})
}
/// Rely on `cargo add` to install dependencies in your sandbox
///
/// https://doc.rust-lang.org/cargo/commands/cargo-add.html
pub fn deps(self, deps: &[&str]) -> Result<Self> {
let Self { lock, root_dir } = &self;
let lock = lock.lock().unwrap();
for dep in deps {
Command::new("cargo")
.args(["add", dep])
.current_dir(root_dir)
.output()?;
}
drop(lock);
Ok(self)
}
/// Evaluate in the Sandbox a given Rust expression
///
/// `quote! { }` would help you to generate a `proc_macro2::TokenStream`
pub fn eval<T: FromStr + ToString>(&self, expr: TokenStream) -> Result<T> {
let Self { lock, root_dir } = self;
let _lock = lock.lock().unwrap();
let wrapper = quote! {
use std::io::prelude::*;
fn main() -> std::io::Result<()> {
let mut file = std::fs::File::create("output")?;
let output = { #expr }.to_string();
file.write_all(output.as_bytes())?;
Ok(())
}
};
fs::write(root_dir.join("src/main.rs"), wrapper.to_string())?;
Command::new("cargo")
.arg("run")
.current_dir(root_dir)
.output()?;
let output = fs::read_to_string(root_dir.join("output"))?
.parse()
.or(Err(io::ErrorKind::Other))?;
fs::remove_file(root_dir.join("output"))?;
Ok(output)
}
}