Build v0 example and SSE slices
This commit is contained in:
+113
-35
@@ -75,6 +75,7 @@ struct Resources {
|
||||
struct Resource {
|
||||
symbol: String,
|
||||
ident: String,
|
||||
component: String,
|
||||
keyed: bool,
|
||||
id: u32,
|
||||
}
|
||||
@@ -94,6 +95,7 @@ struct GeneratedControl {
|
||||
|
||||
impl Resources {
|
||||
fn add_surface(&mut self, root: &Path, path: &Path, surface: &SurfaceDocument) -> io::Result<()> {
|
||||
let component = component_ident(root, path)?;
|
||||
for node in &surface.nodes {
|
||||
let SurfaceNodeKind::Element { tag } = &node.kind else {
|
||||
continue;
|
||||
@@ -103,19 +105,19 @@ impl Resources {
|
||||
reject_unkeyed_loop(surface, node.scope, path, "slot", &name)?;
|
||||
let keyed = is_inside_keyed_for(surface, node.scope);
|
||||
let canonical = canonical_symbol(root, path, &name);
|
||||
self.insert_slot(canonical, name, keyed)?;
|
||||
self.insert_slot(canonical, name, component.clone(), keyed)?;
|
||||
}
|
||||
|
||||
if let Some(name) = static_attr(&node.attrs, "data-slhx-atom") {
|
||||
reject_unkeyed_loop(surface, node.scope, path, "atom", &name)?;
|
||||
let canonical = canonical_symbol(root, path, &name);
|
||||
self.insert_atom(canonical, name)?;
|
||||
self.insert_atom(canonical, name, component.clone())?;
|
||||
}
|
||||
|
||||
if let Some(name) = static_attr(&node.attrs, "data-slhx-handle") {
|
||||
reject_unkeyed_loop(surface, node.scope, path, "handle", &name)?;
|
||||
let canonical = canonical_symbol(root, path, &name);
|
||||
self.insert_handle(canonical, name.clone())?;
|
||||
self.insert_handle(canonical, name.clone(), component.clone())?;
|
||||
|
||||
if tag == "form" {
|
||||
let controls = surface
|
||||
@@ -133,32 +135,33 @@ impl Resources {
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
self.insert_form(canonical_symbol(root, path, &name), name, controls)?;
|
||||
self.insert_form(canonical_symbol(root, path, &name), name, component.clone(), controls)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_slot(&mut self, symbol: String, name: String, keyed: bool) -> io::Result<()> {
|
||||
insert_resource(&mut self.slots, "slot", symbol, name, keyed)
|
||||
fn insert_slot(&mut self, symbol: String, name: String, component: String, keyed: bool) -> io::Result<()> {
|
||||
insert_resource(&mut self.slots, "slot", symbol, name, component, keyed)
|
||||
}
|
||||
|
||||
fn insert_handle(&mut self, symbol: String, name: String) -> io::Result<()> {
|
||||
insert_resource(&mut self.handles, "handle", symbol, name, false)
|
||||
fn insert_handle(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
||||
insert_resource(&mut self.handles, "handle", symbol, name, component, false)
|
||||
}
|
||||
|
||||
fn insert_atom(&mut self, symbol: String, name: String) -> io::Result<()> {
|
||||
insert_resource(&mut self.atoms, "atom", symbol, name, false)
|
||||
fn insert_atom(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
||||
insert_resource(&mut self.atoms, "atom", symbol, name, component, false)
|
||||
}
|
||||
|
||||
fn insert_form(
|
||||
&mut self,
|
||||
symbol: String,
|
||||
name: String,
|
||||
component: String,
|
||||
controls: Vec<GeneratedControl>,
|
||||
) -> io::Result<()> {
|
||||
let resource = make_resource("form", symbol, name, false)?;
|
||||
let resource = make_resource("form", symbol, name, component, false)?;
|
||||
match self.forms.get(&resource.ident) {
|
||||
Some(existing) if existing.resource.symbol != resource.symbol => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
@@ -187,67 +190,111 @@ impl Resources {
|
||||
.join(", ")
|
||||
));
|
||||
|
||||
out.push_str("pub mod slots {\n");
|
||||
for res in self.slots.values() {
|
||||
// req: build/001
|
||||
self.push_resource_modules(&mut out, None, 0);
|
||||
|
||||
for component in self.component_names() {
|
||||
out.push_str("\n#[allow(non_upper_case_globals)]\n");
|
||||
out.push_str(&format!("pub mod {component} {{\n"));
|
||||
self.push_resource_modules(&mut out, Some(&component), 1);
|
||||
out.push_str("}\n");
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn push_resource_modules(&self, out: &mut String, component: Option<&str>, indent: usize) {
|
||||
let pad = " ".repeat(indent);
|
||||
let inner = " ".repeat(indent + 1);
|
||||
let mut handle_ids = Vec::new();
|
||||
|
||||
out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod slots {{\n"));
|
||||
for res in self.slots.values().filter(|res| component_matches(res, component)) {
|
||||
if res.keyed {
|
||||
out.push_str(&format!(
|
||||
" pub const {}: ::slhx::KeyedSlot<::std::string::String, ::std::string::String> = ::slhx::KeyedSlot::new({});\n",
|
||||
"{inner}pub const {}: ::slhx::KeyedSlot<::std::string::String, ::std::string::String> = ::slhx::KeyedSlot::new({});\n",
|
||||
res.ident, res.id
|
||||
));
|
||||
} else {
|
||||
out.push_str(&format!(
|
||||
" pub const {}: ::slhx::Slot<::std::string::String> = ::slhx::Slot::new({});\n",
|
||||
"{inner}pub const {}: ::slhx::Slot<::std::string::String> = ::slhx::Slot::new({});\n",
|
||||
res.ident, res.id
|
||||
));
|
||||
}
|
||||
}
|
||||
out.push_str("}\n\n");
|
||||
out.push_str(&format!("{pad}}}\n\n"));
|
||||
|
||||
out.push_str("pub mod handles {\n");
|
||||
for res in self.handles.values() {
|
||||
out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod handles {{\n"));
|
||||
for res in self.handles.values().filter(|res| component_matches(res, component)) {
|
||||
handle_ids.push(res.id);
|
||||
out.push_str(&format!(
|
||||
" pub const {}: ::slhx::Handle<()> = ::slhx::Handle::new({});\n",
|
||||
"{inner}pub const {}: ::slhx::Handle<()> = ::slhx::Handle::new({});\n",
|
||||
res.ident, res.id
|
||||
));
|
||||
}
|
||||
out.push_str("}\n\n");
|
||||
out.push_str(&format!(
|
||||
"{inner}pub const ALL_IDS: &[u32] = &[{}];\n",
|
||||
handle_ids
|
||||
.into_iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
));
|
||||
out.push_str(&format!("{pad}}}\n\n"));
|
||||
|
||||
out.push_str("pub mod atoms {\n");
|
||||
for res in self.atoms.values() {
|
||||
out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod atoms {{\n"));
|
||||
for res in self.atoms.values().filter(|res| component_matches(res, component)) {
|
||||
out.push_str(&format!(
|
||||
" pub const {}: ::slhx::Atom<::std::string::String> = ::slhx::Atom::new({});\n",
|
||||
"{inner}pub const {}: ::slhx::Atom<::std::string::String> = ::slhx::Atom::new({});\n",
|
||||
res.ident, res.id
|
||||
));
|
||||
}
|
||||
out.push_str("}\n\n");
|
||||
out.push_str(&format!("{pad}}}\n\n"));
|
||||
|
||||
out.push_str("pub mod forms {\n");
|
||||
for form in self.forms.values() {
|
||||
out.push_str(&format!("{pad}#[allow(non_upper_case_globals)]\n{pad}pub mod forms {{\n"));
|
||||
for form in self.forms.values().filter(|form| component_matches(&form.resource, component)) {
|
||||
let res = &form.resource;
|
||||
out.push_str(&format!(
|
||||
" pub const {}: ::slhx::Form<::std::string::String> = ::slhx::Form::new({});\n",
|
||||
"{inner}pub const {}: ::slhx::Form<::std::string::String> = ::slhx::Form::new({});\n",
|
||||
res.ident, res.id
|
||||
));
|
||||
out.push_str(&format!(
|
||||
" pub const {}_CONTRACT: ::slhx::FormContract = ::slhx::FormContract {{ fields: &{}_FIELDS }};\n",
|
||||
"{inner}pub const {}_CONTRACT: ::slhx::FormContract = ::slhx::FormContract {{ fields: &{}_FIELDS }};\n",
|
||||
res.ident.to_ascii_uppercase(), res.ident.to_ascii_uppercase()
|
||||
));
|
||||
out.push_str(&format!(
|
||||
" pub const {}_FIELDS: &[::slhx::FormField] = &[\n",
|
||||
"{inner}pub const {}_FIELDS: &[::slhx::FormField] = &[\n",
|
||||
res.ident.to_ascii_uppercase()
|
||||
));
|
||||
for control in &form.controls {
|
||||
out.push_str(&format!(
|
||||
" ::slhx::FormField {{ name: {}, kind: {}, required: {} }},\n",
|
||||
"{inner} ::slhx::FormField {{ name: {}, kind: {}, required: {} }},\n",
|
||||
rust_str(&control.name),
|
||||
form_control_kind_expr(&control.kind),
|
||||
control.required
|
||||
));
|
||||
}
|
||||
out.push_str(" ];\n");
|
||||
out.push_str(&format!("{inner}];\n"));
|
||||
}
|
||||
out.push_str("}\n");
|
||||
out
|
||||
out.push_str(&format!("{pad}}}\n"));
|
||||
}
|
||||
|
||||
fn component_names(&self) -> Vec<String> {
|
||||
let mut components = Vec::new();
|
||||
for component in self
|
||||
.slots
|
||||
.values()
|
||||
.map(|res| &res.component)
|
||||
.chain(self.handles.values().map(|res| &res.component))
|
||||
.chain(self.atoms.values().map(|res| &res.component))
|
||||
.chain(self.forms.values().map(|form| &form.resource.component))
|
||||
{
|
||||
if !components.contains(component) {
|
||||
components.push(component.clone());
|
||||
}
|
||||
}
|
||||
components.sort();
|
||||
components
|
||||
}
|
||||
|
||||
fn syms(&self) -> String {
|
||||
@@ -306,9 +353,10 @@ fn insert_resource(
|
||||
kind: &str,
|
||||
symbol: String,
|
||||
name: String,
|
||||
component: String,
|
||||
keyed: bool,
|
||||
) -> io::Result<()> {
|
||||
let resource = make_resource(kind, symbol, name, keyed)?;
|
||||
let resource = make_resource(kind, symbol, name, component, keyed)?;
|
||||
match map.get(&resource.ident) {
|
||||
Some(existing) if existing.symbol != resource.symbol => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
@@ -325,7 +373,13 @@ fn insert_resource(
|
||||
}
|
||||
}
|
||||
|
||||
fn make_resource(kind: &str, symbol: String, name: String, keyed: bool) -> io::Result<Resource> {
|
||||
fn make_resource(
|
||||
kind: &str,
|
||||
symbol: String,
|
||||
name: String,
|
||||
component: String,
|
||||
keyed: bool,
|
||||
) -> io::Result<Resource> {
|
||||
let ident = rust_ident(&name).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
@@ -336,6 +390,7 @@ fn make_resource(kind: &str, symbol: String, name: String, keyed: bool) -> io::R
|
||||
Ok(Resource {
|
||||
symbol,
|
||||
ident,
|
||||
component,
|
||||
keyed,
|
||||
id,
|
||||
})
|
||||
@@ -371,6 +426,13 @@ fn static_attr(attrs: &[SurfaceAttribute], name: &str) -> Option<String> {
|
||||
.and_then(|attr| attr.value.clone())
|
||||
}
|
||||
|
||||
fn component_matches(res: &Resource, component: Option<&str>) -> bool {
|
||||
match component {
|
||||
Some(component) => res.component == component,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_inside_keyed_for(surface: &SurfaceDocument, mut scope: ScopeId) -> bool {
|
||||
loop {
|
||||
let Some(current) = surface.scopes.get(scope.0 as usize) else {
|
||||
@@ -418,6 +480,20 @@ fn canonical_symbol(root: &Path, path: &Path, name: &str) -> String {
|
||||
format!("{}::{name}", rel.to_string_lossy().replace('\\', "/"))
|
||||
}
|
||||
|
||||
fn component_ident(root: &Path, path: &Path) -> io::Result<String> {
|
||||
let rel = path.strip_prefix(root).unwrap_or(path);
|
||||
let stem = rel
|
||||
.file_stem()
|
||||
.and_then(|stem| stem.to_str())
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, format!("invalid template path `{}`", path.display())))?;
|
||||
rust_ident(stem).ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("invalid template name `{stem}`; expected a Rust module identifier"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn rust_ident(name: &str) -> Option<String> {
|
||||
let mut chars = name.chars();
|
||||
let first = chars.next()?;
|
||||
@@ -510,6 +586,8 @@ mod tests {
|
||||
assert!(generated.contains("pub mod forms"));
|
||||
assert!(generated.contains("pub mod atoms"));
|
||||
assert!(generated.contains("pub const filter"));
|
||||
assert!(generated.contains("pub mod todo"));
|
||||
assert!(generated.contains("pub const ALL_IDS"));
|
||||
|
||||
let syms = std::fs::read_to_string(out.join("slhx.syms")).unwrap();
|
||||
assert!(syms.contains("atom\t"));
|
||||
|
||||
Reference in New Issue
Block a user