chore(checkpoint): save current v0 build state

This commit is contained in:
slhx agent
2026-05-10 20:51:22 +02:00
parent e0484f8eb6
commit c4e02db5f8
25 changed files with 4095 additions and 508 deletions
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "slhx-derive"
version.workspace = true
edition.workspace = true
[lib]
proc-macro = true
path = "src/lib.rs"
[dependencies]
quote = "1"
syn = { version = "2", features = ["full"] }
+110
View File
@@ -0,0 +1,110 @@
use proc_macro::TokenStream;
use quote::quote;
use std::path::PathBuf;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
let function = parse_macro_input!(item as ItemFn);
let name = function.sig.ident.to_string();
if let Some(syms_path) = syms_path() {
if syms_path.exists() && !syms_contains_handle(&syms_path, &name) {
let message = format!(
"unknown slhx handle `{name}`; add `data-slhx-handle=\"{name}\"` to a template or rename this handler"
);
return quote!(
#function
compile_error!(#message);
)
.into();
}
}
quote!(#function).into()
}
#[proc_macro_attribute]
pub fn surface(_attr: TokenStream, item: TokenStream) -> TokenStream {
inject_surface_include(item)
}
#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn app(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
fn inject_surface_include(item: TokenStream) -> TokenStream {
let item = item.to_string();
let Some(insert_at) = item.rfind('}') else {
return compile_error("#[slhx::surface] must be used on an inline module");
};
let include = surface_include();
let expanded = format!("{}{}{}", &item[..insert_at], include, &item[insert_at..]);
expanded
.parse()
.unwrap_or_else(|_| compile_error("#[slhx::surface] could not expand this module"))
}
fn surface_include() -> String {
let Some(path) = generated_path("slhx.generated.rs") else {
return String::new();
};
if path.exists() {
format!(" include!({:?}); ", path.display().to_string())
} else {
String::new()
}
}
fn syms_path() -> Option<PathBuf> {
generated_path("slhx.syms")
}
fn generated_path(file: &str) -> Option<PathBuf> {
std::env::var_os("OUT_DIR").map(|out_dir| PathBuf::from(out_dir).join(file))
}
fn syms_contains_handle(path: &PathBuf, ident: &str) -> bool {
let Ok(syms) = std::fs::read_to_string(path) else {
return true;
};
syms.lines().any(|line| {
let mut fields = line.split('\t');
matches!(fields.next(), Some("handle"))
&& fields.nth(1).is_some_and(|handle_ident| handle_ident == ident)
})
}
fn compile_error(message: &str) -> TokenStream {
format!("compile_error!({message:?});")
.parse()
.expect("compile_error expansion is valid")
}
#[cfg(test)]
mod tests {
use super::syms_contains_handle;
#[test]
fn syms_lookup_matches_handle_ident() {
let path = std::env::temp_dir().join("slhx-derive-syms-test.syms");
std::fs::write(
&path,
"slhx-syms-v1\nslot\ttemplates/a.heml::count\tcount\t1\nhandle\ttemplates/a.heml::create\tcreate\t2\n",
)
.unwrap();
assert!(syms_contains_handle(&path, "create"));
assert!(!syms_contains_handle(&path, "missing"));
let _ = std::fs::remove_file(path);
}
}
+11
View File
@@ -0,0 +1,11 @@
use slhx_derive::surface;
#[surface]
mod ui {
pub const EXISTING: u8 = 1;
}
#[test]
fn surface_macro_preserves_inline_module_without_generated_file() {
assert_eq!(ui::EXISTING, 1);
}