Add compile-time leaf declarations

Introduce a function-like leaf declaration macro, bind endpoint and TUI hosts to shared generated metadata, and move remote shell endpoint construction out of the leaf module into the examples and runtime assembly code.
This commit is contained in:
Michael Mikovsky
2026-04-26 13:54:44 -06:00
parent fccd61ea29
commit bc22d349bf
17 changed files with 598 additions and 170 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ pub extern crate alloc;
use unshell_protocol::DataMessage;
pub use unshell_macros::{Leaf, Procedure, procedures};
pub use unshell_macros::{Leaf, Procedure, leaf, procedures};
pub use unshell_protocol as protocol;
/// Re-exports one role-specific type behind a stable public alias.
+8 -40
View File
@@ -6,13 +6,10 @@ mod transport;
use std::collections::BTreeMap;
use unshell::Leaf;
use unshell::protocol::tree::{
Call, HookKey, Procedure, ProcedureEffect, ProcedureRuntime, ProcedureStore, ProtocolEndpoint,
};
use unshell::protocol::tree::{Call, HookKey, Procedure, ProcedureEffect, ProcedureStore};
pub use errors::ShellLeafError;
pub use session::ProcedureOpen;
pub use session::Open;
pub use transport::{LISTEN_ADDR, send_forward, spawn_frame_reader, write_frames};
use super::OpenRequest;
@@ -22,25 +19,24 @@ use super::OpenRequest;
/// The endpoint keeps each live shell session in an explicit map keyed by the
/// caller-owned hook identity. That makes ownership and cleanup of hook-backed
/// shell processes easy to inspect during debugging.
#[derive(Default, Leaf)]
#[leaf(leaf_name = "remote_shell")]
#[derive(Default)]
pub struct RemoteShellEndpoint {
sessions: BTreeMap<HookKey, ProcedureOpen>,
sessions: BTreeMap<HookKey, Open>,
}
impl ProcedureStore<ProcedureOpen> for RemoteShellEndpoint {
fn procedure_sessions(&mut self) -> &mut BTreeMap<HookKey, ProcedureOpen> {
impl ProcedureStore<Open> for RemoteShellEndpoint {
fn procedure_sessions(&mut self) -> &mut BTreeMap<HookKey, Open> {
&mut self.sessions
}
}
impl Procedure<RemoteShellEndpoint> for ProcedureOpen {
impl Procedure<RemoteShellEndpoint> for Open {
type Error = ShellLeafError;
type Input = OpenRequest;
fn open(_leaf: &mut RemoteShellEndpoint, call: Call<Self::Input>) -> Result<Self, Self::Error> {
let hook_key = call.response_hook.ok_or(ShellLeafError::MissingHook)?;
ProcedureOpen::spawn(hook_key.return_path, hook_key.hook_id, call.procedure_id)
Open::spawn(hook_key.return_path, hook_key.hook_id, call.procedure_id)
}
fn on_data(
@@ -70,31 +66,3 @@ impl Procedure<RemoteShellEndpoint> for ProcedureOpen {
session.terminate()
}
}
/// Builds the controller endpoint used by the receiver example.
pub fn build_controller_endpoint() -> ProtocolEndpoint {
ProtocolEndpoint::new(
Vec::new(),
None,
vec![unshell::protocol::tree::ChildRoute::registered(agent_path())],
Vec::new(),
)
}
/// Builds the stateful shell runtime used by the endpoint example.
pub fn build_agent_runtime() -> ProcedureRuntime<RemoteShellEndpoint, ProcedureOpen> {
let endpoint = ProtocolEndpoint::new(
agent_path(),
Some(Vec::new()),
Vec::new(),
vec![unshell::protocol::tree::LeafSpec {
name: RemoteShellEndpoint::protocol_leaf_name(),
procedures: vec![ProcedureOpen::protocol_procedure_id()],
}],
);
ProcedureRuntime::new(endpoint, RemoteShellEndpoint::default())
}
fn agent_path() -> Vec<String> {
vec![String::from("agent")]
}
@@ -23,7 +23,7 @@ use super::errors::ShellLeafError;
/// one opening procedure and one live hook remains direct and visible.
#[derive(Procedure)]
#[procedure(leaf = RemoteShellEndpoint, name = "open")]
pub struct ProcedureOpen {
pub struct Open {
/// Spawned PTY child process.
pub(super) child: Box<dyn portable_pty::Child + Send>,
/// Process-group leader used for Unix hangup and kill signaling.
@@ -52,7 +52,7 @@ enum OutputEvent {
ReaderClosed,
}
impl ProcedureOpen {
impl Open {
pub(super) fn spawn(
return_path: Vec<String>,
hook_id: u64,
@@ -213,7 +213,7 @@ impl ProcedureOpen {
}
}
impl Drop for ProcedureOpen {
impl Drop for Open {
fn drop(&mut self) {
let _ = self.terminate();
}
+23
View File
@@ -24,6 +24,29 @@ pub use tui::RemoteShellTui;
#[derive(Archive, Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct OpenRequest;
#[cfg(any(feature = "leaf_endpoint", feature = "leaf_tui"))]
macro_rules! declare_remote_shell_leaf {
($($role_args:tt)*) => {
crate::leaf! {
name = "remote_shell",
procedures = [Open],
$($role_args)*
}
};
}
#[cfg(all(feature = "leaf_endpoint", not(feature = "leaf_tui")))]
declare_remote_shell_leaf!(endpoint_struct = RemoteShellEndpoint,);
#[cfg(all(not(feature = "leaf_endpoint"), feature = "leaf_tui"))]
declare_remote_shell_leaf!(tui_struct = RemoteShellTui,);
#[cfg(all(feature = "leaf_endpoint", feature = "leaf_tui"))]
declare_remote_shell_leaf!(
endpoint_struct = RemoteShellEndpoint,
tui_struct = RemoteShellTui,
);
crate::role_leaf! {
/// Feature-selected remote shell surface.
pub type RemoteShell {
+1 -3
View File
@@ -7,14 +7,12 @@
use std::string::String;
use std::vec::Vec;
use unshell::Leaf;
use unshell::protocol::DataMessage;
use crate::{LeafTui, TuiError};
/// Stub TUI surface for the remote shell leaf.
#[derive(Default, Leaf)]
#[leaf(leaf_name = "remote_shell")]
#[derive(Default)]
pub struct RemoteShellTui {
transcript: Vec<u8>,
}