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}