1#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6#![feature(iterator_try_collect)]
7
8pub mod client;
9pub mod error;
10pub mod script;
11pub mod stdio;
12
13use crate::client::ClientUserData;
14use crate::error::Result;
15use crate::script::ScriptOutput;
16use mlua::{LuaOptions, StdLib, Value, Variadic};
17use nil_client::Client;
18use std::mem;
19use std::sync::Arc;
20use stdio::{Stdio, StdioMessage};
21use tokio::sync::RwLock;
22use tokio::sync::mpsc::{self, UnboundedReceiver};
23
24pub struct Lua {
25 inner: mlua::Lua,
26 stdout: Stdio,
27 stderr: Stdio,
28}
29
30impl Lua {
31 pub fn new(client: &Arc<RwLock<Client>>) -> Result<Self> {
32 Self::with_libs(client, StdLib::MATH | StdLib::STRING | StdLib::TABLE)
33 }
34
35 pub fn with_libs(client: &Arc<RwLock<Client>>, libs: StdLib) -> Result<Self> {
36 let lua = mlua::Lua::new_with(libs, LuaOptions::default())?;
37
38 let globals = lua.globals();
39 let client_data = ClientUserData::new(Arc::clone(client));
40 globals.set("client", lua.create_userdata(client_data)?)?;
41
42 let stdout_rx = pipe_stdio(&lua, "print", "println")?;
43 let stderr_rx = pipe_stdio(&lua, "eprint", "eprintln")?;
44
45 Ok(Self {
46 inner: lua,
47 stdout: Stdio::new(stdout_rx),
48 stderr: Stdio::new(stderr_rx),
49 })
50 }
51
52 pub async fn execute(&mut self, chunk: &str) -> Result<ScriptOutput> {
53 self.flush();
54 self.clear();
55
56 self.inner.load(chunk).exec_async().await?;
57
58 Ok(self.output())
59 }
60
61 fn output(&mut self) -> ScriptOutput {
62 self.flush();
63 self.stdout.buffer.sort();
64 self.stderr.buffer.sort();
65
66 ScriptOutput {
67 stdout: mem::take(&mut self.stdout.buffer),
68 stderr: mem::take(&mut self.stderr.buffer),
69 }
70 }
71
72 fn flush(&mut self) {
73 self.stdout.flush();
74 self.stderr.flush();
75 }
76
77 fn clear(&mut self) {
78 self.stdout.buffer.clear();
79 self.stderr.buffer.clear();
80 }
81}
82
83fn pipe_stdio(
84 lua: &mlua::Lua,
85 name: &str,
86 name_ln: &str,
87) -> Result<UnboundedReceiver<StdioMessage>> {
88 let (tx, rx) = mpsc::unbounded_channel();
89 let create_fn = |line_break: bool| {
90 let tx = tx.clone();
91 lua.create_function(move |_, values: Variadic<Value>| {
92 let mut string = values
93 .into_iter()
94 .map(|it| it.to_string())
95 .try_collect::<String>()?;
96
97 if line_break {
98 string.push('\n');
99 }
100
101 let _ = tx.send(StdioMessage::new(string));
102
103 Ok(())
104 })
105 };
106
107 let globals = lua.globals();
108 globals.set(name, create_fn(false)?)?;
109 globals.set(name_ln, create_fn(true)?)?;
110
111 Ok(rx)
112}