Skip to content

Commit 7cb3abb

Browse files
committed
refactor(pluginlab): implement bin-in-lib pattern for better encapsulation
Move main application logic from binary to library entry point to improve module visibility control and API design. Changes: - Move all main.rs logic to lib.rs::run_async() function - Change module declarations from pub mod to pub(crate) mod - Make internal items pub(crate) to hide from docs and external users - Simplify main.rs to just call library entry point - Add pub(crate) visibility to engine and wasm_host exports Benefits: - Internal modules (api, cli, engine, helpers, etc.) no longer appear in docs - Cleaner public API with only run_async() and explicitly exported items visible - Maintains binary functionality while improving encapsulation - Follows Rust idioms for library/binary separation This refactor ensures that internal implementation details are properly hidden from external users while still allowing the binary to access all necessary functionality through the library's public interface. In the future, modules may change to more open visibility.
1 parent eb38fda commit 7cb3abb

File tree

4 files changed

+204
-195
lines changed

4 files changed

+204
-195
lines changed

crates/pluginlab/src/engine.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ impl WasmEngine {
2727
Ok(Self { engine })
2828
}
2929

30+
#[allow(unused)]
3031
pub fn engine(&self) -> &Engine {
3132
&self.engine
3233
}
@@ -75,6 +76,7 @@ impl WasmEngine {
7576
}
7677

7778
/// Load a WebAssembly component from bytes
79+
#[allow(unused)]
7880
pub fn load_component_from_bytes(&self, bytes: &[u8]) -> Result<Component> {
7981
let component = Component::from_binary(&self.engine, bytes)?;
8082
Ok(component)

crates/pluginlab/src/lib.rs

Lines changed: 200 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,200 @@
1-
pub mod api;
2-
pub mod cli;
3-
mod engine;
4-
pub mod helpers;
5-
pub mod permissions;
6-
mod store;
7-
mod wasm_host;
8-
9-
pub use engine::WasmEngine;
10-
pub use wasm_host::{PluginInstance, WasmHost};
1+
pub(crate) mod api;
2+
pub(crate) mod cli;
3+
pub(crate) mod engine;
4+
pub(crate) mod helpers;
5+
pub(crate) mod permissions;
6+
pub(crate) mod store;
7+
pub(crate) mod wasm_host;
8+
9+
pub(crate) use engine::WasmEngine;
10+
pub(crate) use wasm_host::WasmHost;
11+
12+
use anyhow::Result;
13+
use api::host_api::repl::api::transport;
14+
use clap::Parser;
15+
use cli::Cli;
16+
use helpers::{StatusHandler, StdoutHandler};
17+
use std::io::Write;
18+
19+
/// Main entry point for the REPL application
20+
pub async fn run_async() -> Result<()> {
21+
// Parse command line arguments
22+
let cli = Cli::parse();
23+
let debug = cli.debug;
24+
println!("[Host] Starting REPL host...");
25+
26+
// Create a WASI context for the host
27+
// Binding stdio, args, env, preopened dir ...
28+
let wasi_ctx = WasmEngine::build_wasi_ctx(&cli)?;
29+
30+
// Create the WebAssembly engine
31+
let engine = WasmEngine::new()?;
32+
33+
// Create the host
34+
let mut host = WasmHost::new(&engine, wasi_ctx, &cli);
35+
36+
println!("[Host] Loading REPL logic from: {}", cli.repl_logic);
37+
// Override the REPL logic in the binary with the one passed by params
38+
host.load_repl_logic(&engine, &cli.repl_logic).await?;
39+
40+
// Load plugins
41+
for plugin_source in &cli.plugins {
42+
println!("[Host] Loading plugin: {}", plugin_source);
43+
host.load_plugin(&engine, plugin_source).await?;
44+
}
45+
46+
let mut plugins_config: Vec<(String, String)> = Vec::new();
47+
for (name, plugin_instance) in &host.plugins {
48+
let man = plugin_instance
49+
.plugin
50+
.repl_api_plugin()
51+
.call_man(&mut host.store)
52+
.await?;
53+
plugins_config.push((name.clone(), man));
54+
host.store.data_mut().plugins_names.push(name.clone());
55+
}
56+
if debug {
57+
eprintln!("[Host][Debug] Loaded plugins config: {:?}", plugins_config);
58+
}
59+
60+
host.store
61+
.data_mut()
62+
.repl_vars
63+
.insert("ROOT".to_string(), "/Users".to_string());
64+
host.store
65+
.data_mut()
66+
.repl_vars
67+
.insert("USER".to_string(), "Tophe".to_string());
68+
host.store
69+
.data_mut()
70+
.repl_vars
71+
.insert("?".to_string(), "0".to_string());
72+
if debug {
73+
eprintln!(
74+
"[Host][Debug] Loaded env vars: {:?}",
75+
host.store.data().repl_vars
76+
);
77+
}
78+
79+
let Some(repl_logic) = host.repl_logic else {
80+
return Err(anyhow::anyhow!("No REPL logic loaded"));
81+
};
82+
83+
loop {
84+
let mut line = String::new();
85+
match host.store.data().repl_vars.get("?") {
86+
Some(last_status) => {
87+
print!("repl({})> ", last_status);
88+
}
89+
None => {
90+
print!("repl> ");
91+
}
92+
}
93+
std::io::stdout().flush()?;
94+
std::io::stdin().read_line(&mut line)?;
95+
let result = repl_logic
96+
.repl_api_repl_logic()
97+
.call_readline(&mut host.store, &line)
98+
.await?;
99+
// todo retrieve list of reserved commands from the repl-logic guest
100+
101+
match result {
102+
// The built-in commands run in the repl-logic-guest contain stdout, stderr and a status
103+
// We only need to output them and set the $? variable
104+
transport::ReadlineResponse::Ready(plugin_response) => {
105+
if let Some(stdout) = plugin_response.stdout {
106+
StdoutHandler::print_and_set_last_result(
107+
&mut host.store.data_mut().repl_vars,
108+
stdout,
109+
);
110+
}
111+
if let Some(stderr) = plugin_response.stderr {
112+
eprintln!("{}", stderr);
113+
}
114+
StatusHandler::set_exit_status(
115+
&mut host.store.data_mut().repl_vars,
116+
plugin_response.status
117+
== api::host_api::repl::api::transport::ReplStatus::Success,
118+
);
119+
}
120+
// The repl-logic-guest parses the command and payload (expanded variables)
121+
// We run the command of the plugin from the host, which has access to
122+
// - the plugins
123+
// - the store
124+
transport::ReadlineResponse::ToRun(parsed_line) => {
125+
if debug {
126+
eprintln!("[Host][Debug] To run: {:?}", parsed_line);
127+
}
128+
129+
// empty line - do nothing
130+
if parsed_line.command == "" {
131+
continue;
132+
}
133+
134+
// this is a man command for plugins, we run it from the host
135+
if parsed_line.command == "man" {
136+
let Some(plugin_instance) = host.plugins.get(&parsed_line.payload) else {
137+
println!(
138+
"Unknown command: {}. Try `help` to see available commands.",
139+
parsed_line.payload
140+
);
141+
StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, false);
142+
continue;
143+
};
144+
let man = plugin_instance
145+
.plugin
146+
.repl_api_plugin()
147+
.call_man(&mut host.store)
148+
.await?;
149+
StdoutHandler::print_and_set_last_result(
150+
&mut host.store.data_mut().repl_vars,
151+
man,
152+
);
153+
StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, true);
154+
continue;
155+
}
156+
157+
// this is a plugin command, we run it from the host
158+
match host.plugins.get(&parsed_line.command) {
159+
Some(plugin_instance) => {
160+
let result = plugin_instance
161+
.plugin
162+
.repl_api_plugin()
163+
.call_run(&mut host.store, &parsed_line.payload)
164+
.await?;
165+
if let Ok(result) = result {
166+
if let Some(stdout) = result.stdout {
167+
StdoutHandler::print_and_set_last_result(
168+
&mut host.store.data_mut().repl_vars,
169+
stdout,
170+
);
171+
}
172+
if let Some(stderr) = result.stderr {
173+
eprintln!("{}", stderr);
174+
}
175+
StatusHandler::set_exit_status(
176+
&mut host.store.data_mut().repl_vars,
177+
result.status
178+
== api::plugin_api::repl::api::transport::ReplStatus::Success,
179+
);
180+
} else {
181+
eprintln!("Error: {:?}", result);
182+
StatusHandler::set_exit_status(
183+
&mut host.store.data_mut().repl_vars,
184+
false,
185+
);
186+
}
187+
}
188+
None => {
189+
println!(
190+
"Unknown command: {}. Try `help` to see available commands.",
191+
parsed_line.command
192+
);
193+
StatusHandler::set_exit_status(&mut host.store.data_mut().repl_vars, false);
194+
continue;
195+
}
196+
}
197+
}
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)