antlion/
lib.rs

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