2026-04-25 14:41:00 -06:00
|
|
|
//! Proc macros for `unshell` application-layer leaf declarations.
|
|
|
|
|
|
2026-04-26 12:08:34 -06:00
|
|
|
mod leaf;
|
2026-04-26 13:54:44 -06:00
|
|
|
mod leaf_decl;
|
2026-04-26 12:08:34 -06:00
|
|
|
mod procedure;
|
|
|
|
|
mod procedures;
|
|
|
|
|
mod utils;
|
|
|
|
|
|
2026-04-25 14:41:00 -06:00
|
|
|
use proc_macro::TokenStream;
|
2026-04-26 12:08:34 -06:00
|
|
|
use syn::{DeriveInput, ItemImpl, parse_macro_input};
|
2026-04-25 14:41:00 -06:00
|
|
|
|
2026-04-26 13:54:44 -06:00
|
|
|
/// Declares one compile-time leaf surface and binds it to endpoint and/or TUI
|
|
|
|
|
/// host structs.
|
|
|
|
|
///
|
|
|
|
|
/// What it is: a function-like macro that generates the shared protocol-visible
|
|
|
|
|
/// metadata for one leaf and applies that metadata to the listed host structs.
|
|
|
|
|
///
|
|
|
|
|
/// Why it exists: endpoint and TUI hosts should not each have to repeat the leaf
|
|
|
|
|
/// name and procedure inventory, and endpoint construction should not need a
|
|
|
|
|
/// handwritten list of procedure ids.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// unshell::leaf! {
|
|
|
|
|
/// name = "remote_shell",
|
|
|
|
|
/// procedures = [Open, Reset, whoami],
|
|
|
|
|
/// endpoint_struct = RemoteShellEndpoint,
|
|
|
|
|
/// tui_struct = RemoteShellTui,
|
|
|
|
|
/// }
|
|
|
|
|
/// ```
|
|
|
|
|
#[proc_macro]
|
|
|
|
|
pub fn leaf(input: TokenStream) -> TokenStream {
|
|
|
|
|
match leaf_decl::expand_leaf_declaration(parse_macro_input!(
|
|
|
|
|
input as leaf_decl::LeafDeclarationInput
|
|
|
|
|
)) {
|
|
|
|
|
Ok(tokens) => tokens.into(),
|
|
|
|
|
Err(error) => error.to_compile_error().into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 13:43:06 -06:00
|
|
|
/// Derives canonical protocol-leaf identity helpers for one host type.
|
|
|
|
|
///
|
|
|
|
|
/// What it is: a derive macro that implements `ProtocolLeaf` and generates the
|
|
|
|
|
/// `protocol_leaf_name()` convenience method.
|
|
|
|
|
///
|
|
|
|
|
/// Why it exists: simple leaves and compatibility paths still need a lightweight
|
|
|
|
|
/// way to say "this host type exposes this canonical wire name" without writing
|
|
|
|
|
/// the trait implementation by hand.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// use unshell::Leaf;
|
|
|
|
|
///
|
|
|
|
|
/// #[derive(Leaf)]
|
|
|
|
|
/// #[leaf(leaf_name = "echo")]
|
|
|
|
|
/// struct EchoLeaf;
|
|
|
|
|
///
|
|
|
|
|
/// assert!(EchoLeaf::protocol_leaf_name().contains("echo"));
|
|
|
|
|
/// ```
|
2026-04-25 14:41:00 -06:00
|
|
|
#[proc_macro_derive(Leaf, attributes(leaf))]
|
|
|
|
|
pub fn derive_leaf(input: TokenStream) -> TokenStream {
|
2026-04-26 12:08:34 -06:00
|
|
|
match leaf::expand_leaf(parse_macro_input!(input as DeriveInput)) {
|
2026-04-25 14:41:00 -06:00
|
|
|
Ok(tokens) => tokens.into(),
|
|
|
|
|
Err(error) => error.to_compile_error().into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 13:43:06 -06:00
|
|
|
/// Derives canonical stateful-procedure metadata for one procedure type.
|
|
|
|
|
///
|
|
|
|
|
/// What it is: a derive macro that records one procedure suffix and generates
|
|
|
|
|
/// the canonical `protocol_procedure_id()` helper for that procedure.
|
|
|
|
|
///
|
|
|
|
|
/// Why it exists: hook-backed procedures need one stable `procedure_id`, but the
|
|
|
|
|
/// runtime should not require each procedure to handwrite the identifier logic.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// use unshell::{Leaf, Procedure};
|
|
|
|
|
///
|
|
|
|
|
/// #[derive(Leaf)]
|
|
|
|
|
/// #[leaf(leaf_name = "shell")]
|
|
|
|
|
/// struct ShellLeaf;
|
|
|
|
|
///
|
|
|
|
|
/// #[derive(Procedure)]
|
|
|
|
|
/// #[procedure(leaf = ShellLeaf, name = "open")]
|
|
|
|
|
/// struct OpenSession;
|
|
|
|
|
///
|
|
|
|
|
/// assert!(OpenSession::protocol_procedure_id().ends_with(".open"));
|
|
|
|
|
/// ```
|
2026-04-25 17:42:39 -06:00
|
|
|
#[proc_macro_derive(Procedure, attributes(procedure))]
|
|
|
|
|
pub fn derive_procedure(input: TokenStream) -> TokenStream {
|
2026-04-26 12:08:34 -06:00
|
|
|
match procedure::expand_procedure(parse_macro_input!(input as DeriveInput)) {
|
2026-04-25 17:42:39 -06:00
|
|
|
Ok(tokens) => tokens.into(),
|
|
|
|
|
Err(error) => error.to_compile_error().into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 13:43:06 -06:00
|
|
|
/// Generates dispatch glue for a simple call-driven leaf impl block.
|
|
|
|
|
///
|
|
|
|
|
/// What it is: an attribute macro placed on one `impl` block whose `#[call]`
|
|
|
|
|
/// methods define the callable surface for that leaf.
|
|
|
|
|
///
|
|
|
|
|
/// Why it exists: one-shot leaves should be able to declare a small RPC-like API
|
|
|
|
|
/// on ordinary Rust methods while still producing the canonical procedure list
|
|
|
|
|
/// and dispatch logic expected by the protocol runtime.
|
|
|
|
|
///
|
|
|
|
|
/// # Example
|
|
|
|
|
/// ```ignore
|
|
|
|
|
/// use unshell::{Leaf, procedures};
|
|
|
|
|
///
|
|
|
|
|
/// #[derive(Leaf)]
|
|
|
|
|
/// #[leaf(id = "org.example.v1.echo")]
|
|
|
|
|
/// struct EchoLeaf;
|
|
|
|
|
///
|
|
|
|
|
/// #[procedures(error = core::convert::Infallible)]
|
|
|
|
|
/// impl EchoLeaf {
|
|
|
|
|
/// #[call]
|
|
|
|
|
/// fn echo(&mut self, input: String) -> String {
|
|
|
|
|
/// input
|
|
|
|
|
/// }
|
|
|
|
|
/// }
|
|
|
|
|
///
|
|
|
|
|
/// assert!(EchoLeaf::protocol_procedure_id("echo").is_some());
|
|
|
|
|
/// ```
|
2026-04-25 15:35:08 -06:00
|
|
|
#[proc_macro_attribute]
|
|
|
|
|
pub fn procedures(attr: TokenStream, item: TokenStream) -> TokenStream {
|
2026-04-26 12:08:34 -06:00
|
|
|
match procedures::expand_procedures(
|
|
|
|
|
parse_macro_input!(attr as procedures::ProceduresAttributes),
|
2026-04-25 15:35:08 -06:00
|
|
|
parse_macro_input!(item as ItemImpl),
|
|
|
|
|
) {
|
|
|
|
|
Ok(tokens) => tokens.into(),
|
|
|
|
|
Err(error) => error.to_compile_error().into(),
|
|
|
|
|
}
|
|
|
|
|
}
|