Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate type constructors instead of functions for attributes #8

Merged
merged 1 commit into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 25 additions & 28 deletions examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::BTreeMap;

use ravel_web::{
attr as a, collections::btree_map, el::*, event::*, format_text,
attr::*, collections::btree_map, el::*, event::*, format_text,
run::spawn_body, text::text, View,
};
use web_sys::wasm_bindgen::{JsCast as _, UnwrapThrowExt};
Expand Down Expand Up @@ -53,7 +53,7 @@ impl Filter {
fn button(self, selected: Self) -> View!(Model) {
li(a((
format_text!("{:?}", self),
a::class((selected == self).then_some("selected")),
Class((selected == self).then_some("selected")),
on_(Click, move |model: &mut Model| model.filter = self),
)))
}
Expand All @@ -68,17 +68,17 @@ fn item(filter: Filter, id: usize, item: &Item) -> View!(Model, '_) {

show.then(|| {
li((
a::class((
Class((
item.checked.then_some("completed"),
item.editing.then_some("editing"),
)),
div((
a::class("view"),
Class("view"),
input((
a::type_("checkbox"),
a::class("toggle"),
Type("checkbox"),
Class("toggle"),
// TODO: avoid circular dependency
a::checked(item.checked),
Checked(item.checked),
on(InputEvent, move |model: &mut Model, e| {
let input: web_sys::HtmlInputElement =
e.target().unwrap_throw().dyn_into().unwrap_throw();
Expand All @@ -93,17 +93,14 @@ fn item(filter: Filter, id: usize, item: &Item) -> View!(Model, '_) {
}),
)),
button((
a::class("destroy"),
Class("destroy"),
on_(Click, move |model: &mut Model| {
model.items.remove(&id);
}),
)),
)),
form((
input((
a::class("edit"),
a::value_(a::CloneString(&item.text)),
)),
input((Class("edit"), Value(CloneString(&item.text)))),
on(Active(Submit), move |model: &mut Model, e| {
e.prevent_default();

Expand All @@ -127,15 +124,15 @@ fn item(filter: Filter, id: usize, item: &Item) -> View!(Model, '_) {
fn todomvc(model: &Model) -> View!(Model, '_) {
(
section((
a::class("todoapp"),
Class("todoapp"),
header((
a::class("header"),
Class("header"),
h1("todos"),
form((
input((
a::class("new-todo"),
a::placeholder("What needs to be done?"),
a::autofocus(true),
Class("new-todo"),
Placeholder("What needs to be done?"),
Autofocus(true),
)),
on(Active(Submit), move |model: &mut Model, e| {
e.prevent_default();
Expand All @@ -155,24 +152,24 @@ fn todomvc(model: &Model) -> View!(Model, '_) {
)),
)),
section((
a::class("main"),
Class("main"),
input((
a::id("toggle-all"),
a::class("toggle-all"),
a::type_("checkbox"),
Id("toggle-all"),
Class("toggle-all"),
Type("checkbox"),
)),
label((a::for_("toggle-all"), "Mark all as complete")),
label((For("toggle-all"), "Mark all as complete")),
ul((
a::class("todo-list"),
Class("todo-list"),
btree_map(&model.items, |cx, id, i| {
cx.build(item(model.filter, *id, i))
}),
)),
)),
footer((
a::class("footer"),
Class("footer"),
span((
a::class("todo-count"),
Class("todo-count"),
strong(format_text!(
"{} {} left",
model.count(),
Expand All @@ -183,22 +180,22 @@ fn todomvc(model: &Model) -> View!(Model, '_) {
)),
)),
ul((
a::class("filters"),
Class("filters"),
// TODO: array impls
Filter::All.button(model.filter),
Filter::Active.button(model.filter),
Filter::Completed.button(model.filter),
)),
button((
a::class("clear-completed"),
Class("clear-completed"),
"Clear completed",
on_(Click, move |model: &mut Model| {
model.items.retain(|_, i| !i.checked)
}),
)),
)),
)),
footer((a::class("info"), p("Double-click to edit a todo"))),
footer((Class("info"), p("Double-click to edit a todo"))),
)
}

Expand Down
2 changes: 1 addition & 1 deletion examples/tutorial/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn basic_html() -> View!(Model) {
"Ravel",
// Likewise, HTML attributes defined in the [`attr`] module take
// their value (typically a string) as a parameter.
attr::href("https://github.com/kmicklas/ravel"),
attr::Href("https://github.com/kmicklas/ravel"),
)),
".",
)),
Expand Down
59 changes: 14 additions & 45 deletions ravel-web/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,13 @@ struct Element {

#[derive(Deserialize)]
struct Attribute {
build: Option<String>,
type_name: Option<String>,
value_type: Option<String>,
value_trait: Option<String>,
value_wrapper: Option<String>,
}

impl Attribute {
fn build_name(&self, name: &str) -> String {
match &self.build {
Some(build) => build.clone(),
None => name.replace('-', "_"),
}
}

fn value_trait(&self) -> &str {
assert!(self.value_type.is_none());
self.value_trait.as_deref().unwrap_or("AttrValue")
Expand All @@ -46,7 +39,6 @@ fn main() {
gen_el_types(&config, &out_dir);

gen_attr(&config, &out_dir);
gen_attr_types(&config, &out_dir);
}

fn gen_el_types(config: &Config, out_dir: &std::path::Path) {
Expand Down Expand Up @@ -96,17 +88,25 @@ fn gen_el(config: &Config, out_dir: &std::path::Path) {
println!("cargo::rerun-if-changed=generate.toml");
}

fn gen_attr_types(config: &Config, out_dir: &std::path::Path) {
fn gen_attr(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

// TODO: Per-attr JS snippets like for elements.

for (name, attr) in &config.attribute {
let t = type_name(name);
let t = attr.type_name.clone().unwrap_or(type_name(name));

// Ideally this would be generated by a macro, but rust-analyzer can't
// seem to handle doc attributes generated by a macro generated by a
// build script.
writeln!(&mut src, "/// `{name}` attribute.").unwrap();
writeln!(&mut src, "#[derive(Copy, Clone)]").unwrap();

if let Some(value_type) = &attr.value_type {
assert!(attr.value_trait.is_none());

writeln!(&mut src, "pub struct {t}(pub {value_type});").unwrap();

match &attr.value_wrapper {
Some(value_wrapper) => writeln!(
&mut src,
Expand All @@ -121,6 +121,9 @@ fn gen_attr_types(config: &Config, out_dir: &std::path::Path) {
} else {
let value_trait = attr.value_trait();

writeln!(&mut src, "pub struct {t}<V: {value_trait}>(pub V);")
.unwrap();

match &attr.value_wrapper {
Some(value_wrapper) => writeln!(
&mut src,
Expand All @@ -135,41 +138,7 @@ fn gen_attr_types(config: &Config, out_dir: &std::path::Path) {
}
}

std::fs::write(out_dir.join("gen_attr_types.rs"), src).unwrap();
}

fn gen_attr(config: &Config, out_dir: &std::path::Path) {
let mut src = String::new();

for (name, attr) in &config.attribute {
let build = attr.build_name(name);
let t = type_name(name);
// Ideally this would be generated by a macro, but rust-analyzer can't
// seem to handle doc attributes generated by a macro generated by a
// build script.
writeln!(&mut src, "/// `{name}` attribute.").unwrap();
write!(&mut src, "pub fn {build}").unwrap();

if let Some(value_type) = &attr.value_type {
assert!(attr.value_trait.is_none());

write!(&mut src, "(value: {value_type}) -> types::{t}").unwrap();
} else {
let value_trait = attr.value_trait();

write!(
&mut src,
"<Value: {value_trait}>(value: Value) -> types::{t}<Value>"
)
.unwrap();
}

writeln!(&mut src, " {{ types::{t}(value) }}").unwrap();
}

std::fs::write(out_dir.join("gen_attr.rs"), src).unwrap();

println!("cargo::rerun-if-changed=generate.toml");
}

fn type_name(s: &str) -> String {
Expand Down
26 changes: 13 additions & 13 deletions ravel-web/generate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,16 @@ action = {}
allow = {}
alt = {}
aria-hidden = {} # TODO: enum
as = { build = "as_" } # TODO: enum
async = { build = "async_", value_type = "bool", value_wrapper = "BooleanAttrValue" }
as = {} # TODO: enum
async = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
autocapitalize = {} # TODO: enum
autocomplete = {} # TODO: space-separated, enum?
autofocus = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
autoplay = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
capture = {} # TODO: enum
# charset = {}
checked = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
cite = { build = "cite" }
cite = {}
class = { value_trait = "ClassValue", value_wrapper = "Classes" }
cols = {} # TODO: usize
colspan = {} # TODO: usize
Expand All @@ -167,10 +167,10 @@ controls = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
coords = {} # TODO: custom type
crossorigin = {} # TODO: enum
csp = {}
data = { build = "data" }
data = {}
datetime = {} # TODO: custom type
decoding = {} # TODO: enum
default = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
default = { type_name = "Default_", value_type = "bool", value_wrapper = "BooleanAttrValue" }
defer = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
dir = {} # TODO: enum
dirname = {} # TODO: enum
Expand All @@ -179,8 +179,8 @@ download = {}
draggable = {} # TODO: bool enum!
enctype = {} # TODO: enum
enterkeyhint = {}
for = { build = "for_" }
form = { build = "form" }
for = {}
form = {}
formaction = {}
formenctype = {} # TODO: enum
formmethod = {} # TODO: enum
Expand All @@ -199,11 +199,11 @@ integrity = {}
ismap = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
itemprop = {}
kind = {} # TODO: enum
label = { build = "label" }
label = {}
lang = {}
list = {}
loading = {} # TODO: enum
loop = { build = "loop_", value_type = "bool", value_wrapper = "BooleanAttrValue" }
loop = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
low = {} # TODO: number
max = {} # TODO: number
maxlength = {} # TODO: usize
Expand Down Expand Up @@ -237,8 +237,8 @@ selected = { value_type = "bool", value_wrapper = "BooleanAttrValue" }
shape = {} # TODO: enum
size = {} # TODO: usize
sizes = {}
slot = { build = "slot" }
span = { build = "span" }
slot = {}
span = {}
spellcheck = {} # TODO: bool enum!
src = {}
srcdoc = {}
Expand All @@ -251,8 +251,8 @@ tabindex = {} # TODO: isize
target = {} # TODO: enum
title = {}
translate = {} # TODO: enum
type = { build = "type_" } # TODO: enum
type = {} # TODO: enum
usemap = {}
value = { build = "value_" } # TODO: do we really need this rename?
value = {}
width = {} # TODO: usize
wrap = {} # TODO: enum
Loading